Compare commits
45 Commits
v1.141.1
...
chore/temp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2344939b7d | ||
|
|
f7d9215464 | ||
|
|
9af44fbd69 | ||
|
|
170306af1a | ||
|
|
417d3bbf50 | ||
|
|
e239b8d2fa | ||
|
|
2c7b980eed | ||
|
|
9a5a3c0a1d | ||
|
|
027dab1487 | ||
|
|
e18e4c5962 | ||
|
|
67a8cab286 | ||
|
|
39eee6a634 | ||
|
|
ee98e69097 | ||
|
|
41c1d4d44b | ||
|
|
b21084b851 | ||
|
|
8bf45eb718 | ||
|
|
9d3ca3ad3f | ||
|
|
0090b9db4a | ||
|
|
bee0ae430a | ||
|
|
cc08ebdf80 | ||
|
|
71578b76a7 | ||
|
|
8e5d52abbb | ||
|
|
e52cc259d5 | ||
|
|
00c88b2636 | ||
|
|
e95096d14f | ||
|
|
4c918254b9 | ||
|
|
b97d73d7a7 | ||
|
|
2801b0953d | ||
|
|
7a1c45c364 | ||
|
|
5acd6b70d0 | ||
|
|
edaedca03f | ||
|
|
7b3e1037b6 | ||
|
|
27bc8eba7b | ||
|
|
23fb2e0fae | ||
|
|
59accbf32a | ||
|
|
059a0e8aa8 | ||
|
|
6a55c36762 | ||
|
|
c0bff4b493 | ||
|
|
fd4c2acde8 | ||
|
|
5acf909235 | ||
|
|
fb1458c720 | ||
|
|
255dabc239 | ||
|
|
27751f8fd4 | ||
|
|
72ffa37dd9 | ||
|
|
5a7042364b |
11
.github/workflows/close-duplicates.yml
vendored
11
.github/workflows/close-duplicates.yml
vendored
@@ -35,22 +35,21 @@ jobs:
|
||||
needs: [get_body, should_run]
|
||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||
container:
|
||||
image: yshavit/mdq:0.8.0@sha256:c69224d34224a0043d9a3ee46679ba4a2a25afaac445f293d92afe13cd47fcea
|
||||
image: yshavit/mdq:0.9.0@sha256:4399483ca857fb1a7ed28a596f754c7373e358647de31ce14b79a27c91e1e35e
|
||||
outputs:
|
||||
json: ${{ steps.get_checkbox.outputs.json }}
|
||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||
steps:
|
||||
- id: get_checkbox
|
||||
env:
|
||||
BODY: ${{ needs.get_body.outputs.body }}
|
||||
# TODO: We should detect if the checkbox is missing entirely and also close_and_comment in that case.
|
||||
run: |
|
||||
JSON=$(echo "$BODY" | base64 -d | /mdq --output json '# I have searched | - [?] Yes')
|
||||
echo "json=$JSON" >> $GITHUB_OUTPUT
|
||||
CHECKED=$(echo "$BODY" | base64 -d | /mdq --output json '# I have searched | - [?] Yes' | jq '.items[0].list[0].checked // false')
|
||||
echo "checked=$CHECKED" >> $GITHUB_OUTPUT
|
||||
|
||||
close_and_comment:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get_checkbox_json, should_run]
|
||||
if: ${{ needs.should_run.outputs.should_run == 'true' && !fromJSON(needs.get_checkbox_json.outputs.json).items[0].list[0].checked }}
|
||||
if: ${{ needs.should_run.outputs.should_run == 'true' && needs.get_checkbox_json.outputs.checked != 'true' }}
|
||||
permissions:
|
||||
issues: write
|
||||
discussions: write
|
||||
|
||||
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@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
|
||||
uses: github/codeql-action/init@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0
|
||||
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@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
|
||||
uses: github/codeql-action/autobuild@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0
|
||||
|
||||
# ℹ️ 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@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
|
||||
uses: github/codeql-action/analyze@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
8
.github/workflows/docs-deploy.yml
vendored
8
.github/workflows/docs-deploy.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
run: echo 'The triggering workflow did not succeed' && exit 1
|
||||
- name: Get artifact
|
||||
id: get-artifact
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
return { found: true, id: matchArtifact.id };
|
||||
- name: Determine deploy parameters
|
||||
id: parameters
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
with:
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
|
||||
- name: Load parameters
|
||||
id: parameters
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
env:
|
||||
PARAM_JSON: ${{ needs.checks.outputs.parameters }}
|
||||
with:
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
core.setOutput("shouldDeploy", parameters.shouldDeploy);
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
env:
|
||||
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
|
||||
with:
|
||||
|
||||
2
.github/workflows/fix-format.yml
vendored
2
.github/workflows/fix-format.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
message: 'chore: fix formatting'
|
||||
|
||||
- name: Remove label
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
if: always()
|
||||
with:
|
||||
script: |
|
||||
|
||||
99
.github/workflows/merge-translations.yml
vendored
Normal file
99
.github/workflows/merge-translations.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Merge translations
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
WEBLATE_HOST: 'https://hosted.weblate.org'
|
||||
WEBLATE_COMPONENT: 'immich/immich'
|
||||
|
||||
jobs:
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Find translation PR
|
||||
id: find_pr
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
PR=$(gh pr list --repo $GITHUB_REPOSITORY --author weblate --json number,mergeable)
|
||||
echo "$PR"
|
||||
|
||||
PR_NUMBER=$(echo "$PR" | jq '
|
||||
if length == 1 then
|
||||
.[0].number
|
||||
else
|
||||
error("Expected exactly 1 entry, got \(length)")
|
||||
end
|
||||
' 2>&1) || exit 1
|
||||
|
||||
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
echo "Selected PR $PR_NUMBER"
|
||||
|
||||
if ! echo "$PR" | jq -e '.[0].mergeable == "MERGEABLE"'; then
|
||||
echo "PR is not mergeable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Generate a token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Lock weblate
|
||||
env:
|
||||
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
||||
run: |
|
||||
curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/lock/" -d lock=true
|
||||
|
||||
- name: Commit translations
|
||||
env:
|
||||
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
||||
run: |
|
||||
curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/repository/" -d operation=commit
|
||||
curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/repository/" -d operation=push
|
||||
|
||||
- name: Merge PR
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
PR_NUMBER: ${{ steps.find_pr.outputs.PR_NUMBER }}
|
||||
run: |
|
||||
REVIEW_ID=$(gh api -X POST "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews" --field event='APPROVE' --field body='Automatically merging translations PR' \
|
||||
| jq '.id')
|
||||
echo "REVIEW_ID=$REVIEW_ID" >> $GITHUB_OUTPUT
|
||||
gh pr merge "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --auto --squash
|
||||
|
||||
- name: Wait for PR to merge
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
PR_NUMBER: ${{ steps.find_pr.outputs.PR_NUMBER }}
|
||||
REVIEW_ID: ${{ steps.merge_pr.outputs.REVIEW_ID }}
|
||||
run: |
|
||||
for i in {1..10}; do
|
||||
if gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json merged | jq -e '.merged == true'; then
|
||||
echo "PR merged"
|
||||
exit 0
|
||||
else
|
||||
echo "PR not merged yet, waiting..."
|
||||
sleep 6
|
||||
fi
|
||||
done
|
||||
echo "PR did not merge in time"
|
||||
gh api -X PUT "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews/$REVIEW_ID/dismissals" --field message='Merge attempt timed out' --field event='DISMISS'
|
||||
gh pr merge "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --disable-auto
|
||||
exit 1
|
||||
|
||||
- name: Unlock weblate
|
||||
env:
|
||||
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
||||
run: |
|
||||
curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/lock/" -d lock=false
|
||||
2
.github/workflows/preview-label.yaml
vendored
2
.github/workflows/preview-label.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -129,7 +129,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
|
||||
uses: github/codeql-action/upload-sarif@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.30.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -594,7 +594,7 @@ jobs:
|
||||
contents: read
|
||||
services:
|
||||
postgres:
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:ec713143dca1a426eba2e03707c319e2ec3cc9d304ef767f777f8e297dee820c
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:4f7ee144d4738ad02f6d9376defed7a767b748d185d47eba241578c26a63064b
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
|
||||
23
.github/workflows/weblate-lock.yml
vendored
23
.github/workflows/weblate-lock.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Weblate checks
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
@@ -12,7 +13,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
should_run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}}
|
||||
should_run: ${{ steps.found_paths.outputs.i18n == 'true' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
@@ -32,19 +33,15 @@ jobs:
|
||||
permissions: {}
|
||||
if: ${{ needs.pre-job.outputs.should_run == 'true' }}
|
||||
steps:
|
||||
- name: Check weblate lock
|
||||
- name: Bot review status
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.pull_request_review.pull_request.number }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if [[ "false" = $(curl https://hosted.weblate.org/api/components/immich/immich/lock/ | jq .locked) ]]; then
|
||||
exit 1
|
||||
fi
|
||||
- name: Find Pull Request
|
||||
uses: juliangruber/find-pull-request-action@952b3bb1ddb2dcc0aa3479e98bb1c2d1a922f096 # v1.10.0
|
||||
id: find-pr
|
||||
with:
|
||||
branch: chore/translations
|
||||
- name: Fail if existing weblate PR
|
||||
if: ${{ steps.find-pr.outputs.number }}
|
||||
run: exit 1
|
||||
# Then check for APPROVED by the bot, if absent fail
|
||||
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json reviews | jq -e '.reviews | map(select(.author.login == "github-actions[bot]" and .state == "APPROVED")) | length > 0' \
|
||||
|| (echo "The push-o-matic bot has not approved this PR yet" && exit 1)
|
||||
|
||||
success-check-lock:
|
||||
name: Weblate Lock Check Success
|
||||
needs: [enforce-lock]
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,3 +25,5 @@ mobile/ios/fastlane/report.xml
|
||||
|
||||
vite.config.js.timestamp-*
|
||||
.pnpm-store
|
||||
.devcontainer/library
|
||||
.devcontainer/.env*
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"cli"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@types/byte-size": "^8.1.0",
|
||||
|
||||
@@ -2,37 +2,37 @@
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||
version = "4.52.1"
|
||||
constraints = "4.52.1"
|
||||
version = "4.52.3"
|
||||
constraints = "4.52.3"
|
||||
hashes = [
|
||||
"h1:2lHvafwGbLdmc9lYkuJFw3nsInaQjRpjX/JfIRKmq/M=",
|
||||
"h1:596JomwjrtUrOSreq9NNCS+rj70+jOV+0pfja5MXiTI=",
|
||||
"h1:7mBOA5TVAIt3qAwPXKCtE0RSYeqij9v30mnksuBbpEg=",
|
||||
"h1:ELVgzh4kHKBCYdL+2A8JjWS0E1snLUN3Mmz3Vo6qSfw=",
|
||||
"h1:FGGM5yLFf72g3kSXM3LAN64Gf/AkXr5WCmhixgnP+l4=",
|
||||
"h1:JupkJbQALcIVoMhHImrLeLDsQR1ET7VJLGC7ONxjqGU=",
|
||||
"h1:KsaE4JNq+1uV1nJsuTcYar/8lyY6zKS5UBEpfYg3wvc=",
|
||||
"h1:NHZ5RJIzQDLhie/ykl3uI6UPfNQR9Lu5Ti7JPR6X904=",
|
||||
"h1:NfAuMbn6LQPLDtJhbzO1MX9JMIGLMa8K6CpekvtsuX8=",
|
||||
"h1:e+vNKokamDsp/kJvFr2pRudzwEz2r49iZ/oSggw+1LY=",
|
||||
"h1:jnb4VdfNZ79I3yj7Q8x+JmOT+FxbfjjRfrF0dL0yCW8=",
|
||||
"h1:kmF//O539d7NuHU7qIxDj7Wz4eJmLKFiI5glwQivldU=",
|
||||
"h1:s6XriaKwOgV4jvKAGPXkrxhhOQxpNU5dceZwi9Z/1k8=",
|
||||
"h1:wt3WBEBAeSGTlC9OlnTlAALxRiK4SQgLy0KgBIS7qzs=",
|
||||
"zh:2fb95e1d3229b9b6c704e1a413c7481c60f139780d9641f657b6eb9b633b90f2",
|
||||
"zh:379c7680983383862236e9e6e720c3114195c40526172188e88d0ffcf50dfe2e",
|
||||
"zh:55533beb6cfc02d22ffda8cba8027bc2c841bb172cd637ed0d28323d41395f8f",
|
||||
"zh:5abd70760e4eb1f37a1c307cbd2989ea7c9ba0afb93818c67c1d363a31f75703",
|
||||
"zh:699f1c8cd66129176fe659ebf0e6337632a8967a28d2630b6ae5948665c0c2ae",
|
||||
"zh:69c15acd73c451e89de6477059cda2f3ec200b48ae4b9ff3646c4d389fd3205e",
|
||||
"zh:6e02b687de21b844f8266dff99e93e7c61fc8eb688f4bbb23803caceb251839e",
|
||||
"zh:7a51d17b87ed87b7bebf2ad9fc7c3a74f16a1b44eee92c779c08eb89258c0496",
|
||||
"zh:88ad84436837b0f55302f22748505972634e87400d6902260fd6b7ba1610f937",
|
||||
"h1:3jU62KY4Oj3xzMwkTQWon1nlIvFkgTCqI93IzUGaa0c=",
|
||||
"h1:BWimtYXrvbzbbuoVcyobjQnXjjOb9X69JFTw+GuPxfk=",
|
||||
"h1:C/KvLEm8dVQ6zG2X4asLDtmw2JW/xu7E8MddtaXniO0=",
|
||||
"h1:Doo0xcLFf+CnfDWjsA7G1NvSLURuwcgyVy8k0NF1gJA=",
|
||||
"h1:Gc3FGDtR8lUWsi9VImnnE5/USDXiIwYsv4Hbl+d2lwY=",
|
||||
"h1:HsDY6s1gup5fW9TeuTUy85QMIld1nDOUFlwsfxIq1ig=",
|
||||
"h1:MnHkB56E4b/kT6WZigsZJnB5rgnCfDVbrLBNxIsEXPY=",
|
||||
"h1:O/FUQEqhtknJNdsaMbIBi2pLWBds2VvN5FsTVVntzb0=",
|
||||
"h1:OKQBynkp0J5DIf5FOl/NR3S2rvh89pY+t5wevYxdTJs=",
|
||||
"h1:On+vPsYV8U/J/8wFZPXjeAgNJqFFQj42vNOKuNKURkY=",
|
||||
"h1:SPkrMRJahxK0uum7FnUugbGN/JepHMH8M71DBtYrvG0=",
|
||||
"h1:bEh1ASPMiin3F36+hTfjMQTBnuDl2DzjzSCdova3JEM=",
|
||||
"h1:dtIK+x5Q1sh5SMPaHBHXhL9XDIqbRW0EBmVZ+KHQB8E=",
|
||||
"h1:kZcwWfODMWWyauZ66oaO/X+xXkqBtrbYwfUFEtspwEc=",
|
||||
"zh:53946fce4a631f1d98c61550821c88edede9169dfe5cc254e09a2ab207f76b3f",
|
||||
"zh:61654a21f1dd4331492d4ef77e9ebff066bc01e1281f92b925e5697c9138d681",
|
||||
"zh:6a54e9d129b276f052a2f1b73ad0b8735fe6a7403c6a8f6aa111e525eeefaf35",
|
||||
"zh:7692374e655c346a630b5a7cd776c5e0b2388900dcd7ab69a3af85d0c31c6c43",
|
||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||
"zh:8d46c3d9f4f7ad20ac6ef01daa63f4e30a2d16dcb1bb5c7c7ee3dc6be38e9ca1",
|
||||
"zh:913d64e72a4929dae1d4793e2004f4f9a58b138ea337d9d94fa35cafbf06550a",
|
||||
"zh:c8d93cf86e2e49f6cec665cfe78b82c144cce15a8b2e30f343385fadd1251849",
|
||||
"zh:cc4f69397d9bc34a528a5609a024c3a48f54f21616c0008792dd417297add955",
|
||||
"zh:df99cdb8b064aad35ffea77e645cf6541d0b1b2ebc51b6d26c42031de60ab69e",
|
||||
"zh:8fe5b792a4d2b1c3a0e573649642962494faa00299baa6aaf813b9a43203dc02",
|
||||
"zh:a0f403a4862df90f09de65c6e939d6cfd069a8dda2dd33f82948bf6f5f1124ef",
|
||||
"zh:a25dc3eb60777b600f8f125d321fe7c50b811c5302b58e9a727ceb749a04e35d",
|
||||
"zh:a2f2ac7dc703c69d2e8c67c9cb5620b5348cb4fd6b98515fbe3f478517b56602",
|
||||
"zh:d452e7bd24445ee14166470cf50f3aca566d46cab5f26f1c5c988c0f3106b697",
|
||||
"zh:e10a52b0294735659eb3f0821ad2006ec097918efe58d31d37a5e3c47efef5f6",
|
||||
"zh:e28dd0954cef9f05adf4d4b440d6f134f605344dfa56307181996675e6550af2",
|
||||
"zh:f1e3b2f43a472280442f01ba71a3c06c9167432e553381132ea5c4a77e0b6dd5",
|
||||
"zh:f71fd63718d38fd43829861e91fe79e16d7b4c7c3d508ae3d077368d89b8e5a0",
|
||||
"zh:faf8d3da4b819c4ae8e565d2b1a684c6a948a086cb299189a5e7b30b2178409d",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ terraform {
|
||||
required_providers {
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = "4.52.1"
|
||||
version = "4.52.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,37 +2,37 @@
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||
version = "4.52.1"
|
||||
constraints = "4.52.1"
|
||||
version = "4.52.3"
|
||||
constraints = "4.52.3"
|
||||
hashes = [
|
||||
"h1:2lHvafwGbLdmc9lYkuJFw3nsInaQjRpjX/JfIRKmq/M=",
|
||||
"h1:596JomwjrtUrOSreq9NNCS+rj70+jOV+0pfja5MXiTI=",
|
||||
"h1:7mBOA5TVAIt3qAwPXKCtE0RSYeqij9v30mnksuBbpEg=",
|
||||
"h1:ELVgzh4kHKBCYdL+2A8JjWS0E1snLUN3Mmz3Vo6qSfw=",
|
||||
"h1:FGGM5yLFf72g3kSXM3LAN64Gf/AkXr5WCmhixgnP+l4=",
|
||||
"h1:JupkJbQALcIVoMhHImrLeLDsQR1ET7VJLGC7ONxjqGU=",
|
||||
"h1:KsaE4JNq+1uV1nJsuTcYar/8lyY6zKS5UBEpfYg3wvc=",
|
||||
"h1:NHZ5RJIzQDLhie/ykl3uI6UPfNQR9Lu5Ti7JPR6X904=",
|
||||
"h1:NfAuMbn6LQPLDtJhbzO1MX9JMIGLMa8K6CpekvtsuX8=",
|
||||
"h1:e+vNKokamDsp/kJvFr2pRudzwEz2r49iZ/oSggw+1LY=",
|
||||
"h1:jnb4VdfNZ79I3yj7Q8x+JmOT+FxbfjjRfrF0dL0yCW8=",
|
||||
"h1:kmF//O539d7NuHU7qIxDj7Wz4eJmLKFiI5glwQivldU=",
|
||||
"h1:s6XriaKwOgV4jvKAGPXkrxhhOQxpNU5dceZwi9Z/1k8=",
|
||||
"h1:wt3WBEBAeSGTlC9OlnTlAALxRiK4SQgLy0KgBIS7qzs=",
|
||||
"zh:2fb95e1d3229b9b6c704e1a413c7481c60f139780d9641f657b6eb9b633b90f2",
|
||||
"zh:379c7680983383862236e9e6e720c3114195c40526172188e88d0ffcf50dfe2e",
|
||||
"zh:55533beb6cfc02d22ffda8cba8027bc2c841bb172cd637ed0d28323d41395f8f",
|
||||
"zh:5abd70760e4eb1f37a1c307cbd2989ea7c9ba0afb93818c67c1d363a31f75703",
|
||||
"zh:699f1c8cd66129176fe659ebf0e6337632a8967a28d2630b6ae5948665c0c2ae",
|
||||
"zh:69c15acd73c451e89de6477059cda2f3ec200b48ae4b9ff3646c4d389fd3205e",
|
||||
"zh:6e02b687de21b844f8266dff99e93e7c61fc8eb688f4bbb23803caceb251839e",
|
||||
"zh:7a51d17b87ed87b7bebf2ad9fc7c3a74f16a1b44eee92c779c08eb89258c0496",
|
||||
"zh:88ad84436837b0f55302f22748505972634e87400d6902260fd6b7ba1610f937",
|
||||
"h1:3jU62KY4Oj3xzMwkTQWon1nlIvFkgTCqI93IzUGaa0c=",
|
||||
"h1:BWimtYXrvbzbbuoVcyobjQnXjjOb9X69JFTw+GuPxfk=",
|
||||
"h1:C/KvLEm8dVQ6zG2X4asLDtmw2JW/xu7E8MddtaXniO0=",
|
||||
"h1:Doo0xcLFf+CnfDWjsA7G1NvSLURuwcgyVy8k0NF1gJA=",
|
||||
"h1:Gc3FGDtR8lUWsi9VImnnE5/USDXiIwYsv4Hbl+d2lwY=",
|
||||
"h1:HsDY6s1gup5fW9TeuTUy85QMIld1nDOUFlwsfxIq1ig=",
|
||||
"h1:MnHkB56E4b/kT6WZigsZJnB5rgnCfDVbrLBNxIsEXPY=",
|
||||
"h1:O/FUQEqhtknJNdsaMbIBi2pLWBds2VvN5FsTVVntzb0=",
|
||||
"h1:OKQBynkp0J5DIf5FOl/NR3S2rvh89pY+t5wevYxdTJs=",
|
||||
"h1:On+vPsYV8U/J/8wFZPXjeAgNJqFFQj42vNOKuNKURkY=",
|
||||
"h1:SPkrMRJahxK0uum7FnUugbGN/JepHMH8M71DBtYrvG0=",
|
||||
"h1:bEh1ASPMiin3F36+hTfjMQTBnuDl2DzjzSCdova3JEM=",
|
||||
"h1:dtIK+x5Q1sh5SMPaHBHXhL9XDIqbRW0EBmVZ+KHQB8E=",
|
||||
"h1:kZcwWfODMWWyauZ66oaO/X+xXkqBtrbYwfUFEtspwEc=",
|
||||
"zh:53946fce4a631f1d98c61550821c88edede9169dfe5cc254e09a2ab207f76b3f",
|
||||
"zh:61654a21f1dd4331492d4ef77e9ebff066bc01e1281f92b925e5697c9138d681",
|
||||
"zh:6a54e9d129b276f052a2f1b73ad0b8735fe6a7403c6a8f6aa111e525eeefaf35",
|
||||
"zh:7692374e655c346a630b5a7cd776c5e0b2388900dcd7ab69a3af85d0c31c6c43",
|
||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||
"zh:8d46c3d9f4f7ad20ac6ef01daa63f4e30a2d16dcb1bb5c7c7ee3dc6be38e9ca1",
|
||||
"zh:913d64e72a4929dae1d4793e2004f4f9a58b138ea337d9d94fa35cafbf06550a",
|
||||
"zh:c8d93cf86e2e49f6cec665cfe78b82c144cce15a8b2e30f343385fadd1251849",
|
||||
"zh:cc4f69397d9bc34a528a5609a024c3a48f54f21616c0008792dd417297add955",
|
||||
"zh:df99cdb8b064aad35ffea77e645cf6541d0b1b2ebc51b6d26c42031de60ab69e",
|
||||
"zh:8fe5b792a4d2b1c3a0e573649642962494faa00299baa6aaf813b9a43203dc02",
|
||||
"zh:a0f403a4862df90f09de65c6e939d6cfd069a8dda2dd33f82948bf6f5f1124ef",
|
||||
"zh:a25dc3eb60777b600f8f125d321fe7c50b811c5302b58e9a727ceb749a04e35d",
|
||||
"zh:a2f2ac7dc703c69d2e8c67c9cb5620b5348cb4fd6b98515fbe3f478517b56602",
|
||||
"zh:d452e7bd24445ee14166470cf50f3aca566d46cab5f26f1c5c988c0f3106b697",
|
||||
"zh:e10a52b0294735659eb3f0821ad2006ec097918efe58d31d37a5e3c47efef5f6",
|
||||
"zh:e28dd0954cef9f05adf4d4b440d6f134f605344dfa56307181996675e6550af2",
|
||||
"zh:f1e3b2f43a472280442f01ba71a3c06c9167432e553381132ea5c4a77e0b6dd5",
|
||||
"zh:f71fd63718d38fd43829861e91fe79e16d7b4c7c3d508ae3d077368d89b8e5a0",
|
||||
"zh:faf8d3da4b819c4ae8e565d2b1a684c6a948a086cb299189a5e7b30b2178409d",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ terraform {
|
||||
required_providers {
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = "4.52.1"
|
||||
version = "4.52.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,13 +143,13 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:8d292bdb796aa58bbbaa47fe971c8516f6f57d6a47e7172e62754feb6ed4e7b0
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
||||
@@ -56,14 +56,14 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:8d292bdb796aa58bbbaa47fe971c8516f6f57d6a47e7172e62754feb6ed4e7b0
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
||||
@@ -49,14 +49,14 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:8d292bdb796aa58bbbaa47fe971c8516f6f57d6a47e7172e62754feb6ed4e7b0
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
|
||||
@@ -66,7 +66,7 @@ The provided file is just a starting point. There are a ton of ways to configure
|
||||
After bringing down the containers with `docker compose down` and back up with `docker compose up -d`, a Prometheus instance will now collect metrics from the immich server and microservices containers. Note that we didn't need to expose any new ports for these containers - the communication is handled in the internal Docker network.
|
||||
|
||||
:::note
|
||||
To see exactly what metrics are made available, you can additionally add `8081:8081` to the server container's ports and `8082:8082` to the microservices container's ports.
|
||||
To see exactly what metrics are made available, you can additionally add `8081:8081` (API metrics) and `8082:8082` (microservices metrics) to the immich_server container's ports.
|
||||
Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects.
|
||||
To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](/docs/install/environment-variables/#general).
|
||||
:::
|
||||
|
||||
@@ -147,7 +147,10 @@ SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config';
|
||||
### File properties
|
||||
|
||||
```sql title="Without thumbnails"
|
||||
SELECT * FROM "asset" WHERE "asset"."previewPath" IS NULL OR "asset"."thumbnailPath" IS NULL;
|
||||
SELECT * FROM "asset"
|
||||
WHERE (NOT EXISTS (SELECT 1 FROM "asset_file" WHERE "asset"."id" = "asset_file"."assetId" AND "asset_file"."type" = 'thumbnail')
|
||||
OR NOT EXISTS (SELECT 1 FROM "asset_file" WHERE "asset"."id" = "asset_file"."assetId" AND "asset_file"."type" = 'preview'))
|
||||
AND "asset"."visibility" = 'timeline';
|
||||
```
|
||||
|
||||
```sql title="Failed file movements"
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"classnames": "^2.3.2",
|
||||
"clsx": "^2.0.0",
|
||||
"docusaurus-lunr-search": "^3.3.2",
|
||||
"docusaurus-preset-openapi": "^0.7.5",
|
||||
"lunr": "^2.3.9",
|
||||
|
||||
@@ -105,6 +105,11 @@ const projects: CommunityProjectProps[] = [
|
||||
description: 'Speed up your machine learning by load balancing your requests to multiple computers',
|
||||
url: 'https://github.com/apetersson/immich_ml_balancer',
|
||||
},
|
||||
{
|
||||
title: 'Immich Drop Uploader',
|
||||
description: 'A tiny, zero-login web app for collecting photos/videos from anyone into your Immich server.',
|
||||
url: 'https://github.com/Nasogaa/immich-drop',
|
||||
},
|
||||
];
|
||||
|
||||
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"author": "",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@immich/cli": "file:../cli",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
@@ -31,7 +30,6 @@
|
||||
"@types/pg": "^8.15.1",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@vitest/coverage-v8": "^3.0.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
|
||||
@@ -1466,10 +1466,10 @@ describe('/asset', () => {
|
||||
expectedDate: '2023-04-04T04:00:00.000Z',
|
||||
},
|
||||
{
|
||||
name: 'CreateDate when DateTimeOriginal missing',
|
||||
name: 'CreationDate when DateTimeOriginal missing',
|
||||
exifData: {
|
||||
CreateDate: '2023:05:05 05:00:00', // TESTABLE
|
||||
CreationDate: '2023:07:07 07:00:00', // TESTABLE
|
||||
CreationDate: '2023:05:05 05:00:00', // TESTABLE
|
||||
CreateDate: '2023:07:07 07:00:00', // TESTABLE
|
||||
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
|
||||
},
|
||||
expectedDate: '2023-05-05T05:00:00.000Z',
|
||||
|
||||
@@ -23,8 +23,8 @@ describe('/partners', () => {
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
createPartner({ id: user2.userId }, { headers: asBearerAuth(user1.accessToken) }),
|
||||
createPartner({ id: user1.userId }, { headers: asBearerAuth(user2.accessToken) }),
|
||||
createPartner({ partnerCreateDto: { sharedWithId: user2.userId } }, { headers: asBearerAuth(user1.accessToken) }),
|
||||
createPartner({ partnerCreateDto: { sharedWithId: user1.userId } }, { headers: asBearerAuth(user2.accessToken) }),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -462,7 +462,8 @@ export const utils = {
|
||||
updateLibrary: (accessToken: string, id: string, dto: UpdateLibraryDto) =>
|
||||
updateLibrary({ id, updateLibraryDto: dto }, { headers: asBearerAuth(accessToken) }),
|
||||
|
||||
createPartner: (accessToken: string, id: string) => createPartner({ id }, { headers: asBearerAuth(accessToken) }),
|
||||
createPartner: (accessToken: string, id: string) =>
|
||||
createPartner({ partnerCreateDto: { sharedWithId: id } }, { headers: asBearerAuth(accessToken) }),
|
||||
|
||||
updateMyPreferences: (accessToken: string, userPreferencesUpdateDto: UserPreferencesUpdateDto) =>
|
||||
updateMyPreferences({ userPreferencesUpdateDto }, { headers: asBearerAuth(accessToken) }),
|
||||
|
||||
14
i18n/en.json
14
i18n/en.json
@@ -597,8 +597,6 @@
|
||||
"backup_setting_subtitle": "Manage background and foreground upload settings",
|
||||
"backup_settings_subtitle": "Manage upload settings",
|
||||
"backward": "Backward",
|
||||
"beta_sync": "Beta Sync Status",
|
||||
"beta_sync_subtitle": "Manage the new sync system",
|
||||
"biometric_auth_enabled": "Biometric authentication enabled",
|
||||
"biometric_locked_out": "You are locked out of biometric authentication",
|
||||
"biometric_no_options": "No biometric options available",
|
||||
@@ -1076,10 +1074,7 @@
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "This feature loads external resources from Google in order to work.",
|
||||
"general": "General",
|
||||
"geolocation_instruction_all_have_location": "All assets for this date already have location data. Try showing all assets or select a different date",
|
||||
"geolocation_instruction_location": "Click on an asset with GPS coordinates to use its location, or select a location directly from the map",
|
||||
"geolocation_instruction_no_date": "Select a date to manage location data for photos and videos from that day",
|
||||
"geolocation_instruction_no_photos": "No photos or videos found for this date. Select a different date to show them",
|
||||
"get_help": "Get Help",
|
||||
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
|
||||
"getting_started": "Getting Started",
|
||||
@@ -1520,7 +1515,7 @@
|
||||
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_readonly_mode": "Read-only mode enabled. Double-tap the user avatar icon to exit.",
|
||||
"profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.",
|
||||
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
|
||||
"profile_image_of_user": "Profile image of {user}",
|
||||
@@ -1645,6 +1640,7 @@
|
||||
"restore_user": "Restore user",
|
||||
"restored_asset": "Restored asset",
|
||||
"resume": "Resume",
|
||||
"resume_paused_jobs": "Resume {count, plural, one {# paused job} other {# paused jobs}}",
|
||||
"retry_upload": "Retry upload",
|
||||
"review_duplicates": "Review duplicates",
|
||||
"review_large_files": "Review large files",
|
||||
@@ -1849,10 +1845,8 @@
|
||||
"shift_to_permanent_delete": "press ⇧ to permanently delete asset",
|
||||
"show_album_options": "Show album options",
|
||||
"show_albums": "Show albums",
|
||||
"show_all_assets": "Show all assets",
|
||||
"show_all_people": "Show all people",
|
||||
"show_and_hide_people": "Show & hide people",
|
||||
"show_assets_without_location": "Show assets without location",
|
||||
"show_file_location": "Show file location",
|
||||
"show_gallery": "Show gallery",
|
||||
"show_hidden_people": "Show hidden people",
|
||||
@@ -1923,6 +1917,8 @@
|
||||
"sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums",
|
||||
"sync_local": "Sync Local",
|
||||
"sync_remote": "Sync Remote",
|
||||
"sync_status": "Sync Status",
|
||||
"sync_status_subtitle": "View and manage the sync system",
|
||||
"sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich",
|
||||
"tag": "Tag",
|
||||
"tag_assets": "Tag assets",
|
||||
@@ -1982,6 +1978,7 @@
|
||||
"trash_page_select_assets_btn": "Select assets",
|
||||
"trash_page_title": "Trash ({count})",
|
||||
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
|
||||
"troubleshoot": "Troubleshoot",
|
||||
"type": "Type",
|
||||
"unable_to_change_pin_code": "Unable to change PIN code",
|
||||
"unable_to_setup_pin_code": "Unable to setup PIN code",
|
||||
@@ -2037,7 +2034,6 @@
|
||||
"use_biometric": "Use biometric",
|
||||
"use_current_connection": "use current connection",
|
||||
"use_custom_date_range": "Use custom date range instead",
|
||||
"use_this_location": "Click to use location",
|
||||
"user": "User",
|
||||
"user_has_been_deleted": "This user has been deleted.",
|
||||
"user_id": "User ID",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ARG DEVICE=cpu
|
||||
|
||||
FROM python:3.11-bookworm@sha256:c642d5dfaf9115a12086785f23008558ae2e13bcd0c4794536340bcb777a4381 AS builder-cpu
|
||||
FROM python:3.11-bookworm@sha256:fc1f2e357c307c4044133952b203e66a47e7726821a664f603a180a0c5823844 AS builder-cpu
|
||||
|
||||
FROM builder-cpu AS builder-openvino
|
||||
|
||||
@@ -68,11 +68,11 @@ RUN if [ "$DEVICE" = "rocm" ]; then \
|
||||
uv pip install /opt/onnxruntime_rocm-*.whl; \
|
||||
fi
|
||||
|
||||
FROM python:3.11-slim-bookworm@sha256:838ff46ae6c481e85e369706fa3dea5166953824124735639f3c9f52af85f319 AS prod-cpu
|
||||
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-cpu
|
||||
|
||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2
|
||||
|
||||
FROM python:3.11-slim-bookworm@sha256:838ff46ae6c481e85e369706fa3dea5166953824124735639f3c9f52af85f319 AS prod-openvino
|
||||
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-openvino
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||
|
||||
93
machine-learning/uv.lock
generated
93
machine-learning/uv.lock
generated
@@ -1341,7 +1341,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "locust"
|
||||
version = "2.39.1"
|
||||
version = "2.40.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "configargparse" },
|
||||
@@ -1353,6 +1353,7 @@ dependencies = [
|
||||
{ name = "locust-cloud" },
|
||||
{ name = "msgpack" },
|
||||
{ name = "psutil" },
|
||||
{ name = "pytest" },
|
||||
{ name = "python-engineio" },
|
||||
{ name = "python-socketio", extra = ["client"] },
|
||||
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||
@@ -1360,12 +1361,12 @@ dependencies = [
|
||||
{ name = "requests" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.12'" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/c8/10aa5445c404eed389b56877e6714c1787190cc09dd70059ce3765979ec5/locust-2.39.1.tar.gz", hash = "sha256:6bdd19e27edf9a1c84391d6cf6e9a737dfb832be7dfbf39053191ae31b9cc498", size = 1409902, upload-time = "2025-08-29T17:41:01.544Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/e0/a99401e233ad1b9ad26265ad8f45f2466abb6ef954e7747e8484864eb6df/locust-2.40.2.tar.gz", hash = "sha256:9ffdf900d1ad949d4c5809e2a4e526bba582175f025f24da2755f43f4b5cb23e", size = 1411854, upload-time = "2025-09-08T12:55:28.664Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/b3/b2f4b2ca88b1e72eba7be2b2982533b887f8b709d222db78eb9602aa5121/locust-2.39.1-py3-none-any.whl", hash = "sha256:fd5148f2f1a4ed34aee968abc4393674e69d1b5e1b54db50a397f6eb09ce0b04", size = 1428155, upload-time = "2025-08-29T17:41:00.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/e7/85ddb125d91b3a2bfa2a52eeae2d4c7da062239aaa475d6aebddb5688f41/locust-2.40.2-py3-none-any.whl", hash = "sha256:c8f0060d2bd8479034e9e61e6473669c4c8216930d99ee61ec0e627340b89d3e", size = 1430483, upload-time = "2025-09-08T12:55:25.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2204,7 +2205,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.1"
|
||||
version = "8.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@@ -2215,9 +2216,9 @@ dependencies = [
|
||||
{ name = "pygments" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2249,14 +2250,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest-mock"
|
||||
version = "3.14.1"
|
||||
version = "3.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/99/3323ee5c16b3637b4d941c362182d3e749c11e400bea31018c42219f3a98/pytest_mock-3.15.0.tar.gz", hash = "sha256:ab896bd190316b9d5d87b277569dfcdf718b2d049a2ccff5f7aca279c002a1cf", size = 33838, upload-time = "2025-09-04T20:57:48.679Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b3/7fefc43fb706380144bcd293cc6e446e6f637ddfa8b83f48d1734156b529/pytest_mock-3.15.0-py3-none-any.whl", hash = "sha256:ef2219485fb1bd256b00e7ad7466ce26729b30eadfc7cbcdb4fa9a92ca68db6f", size = 10050, upload-time = "2025-09-04T20:57:47.274Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2532,28 +2533,28 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.11"
|
||||
version = "0.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103, upload-time = "2025-08-28T13:59:08.87Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/1a/1f4b722862840295bcaba8c9e5261572347509548faaa99b2d57ee7bfe6a/ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60", size = 5372863, upload-time = "2025-09-10T16:25:37.917Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885, upload-time = "2025-08-28T13:58:26.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364, upload-time = "2025-08-28T13:58:30.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111, upload-time = "2025-08-28T13:58:33.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060, upload-time = "2025-08-28T13:58:35.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848, upload-time = "2025-08-28T13:58:38.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288, upload-time = "2025-08-28T13:58:40.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633, upload-time = "2025-08-28T13:58:42.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430, upload-time = "2025-08-28T13:58:44.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133, upload-time = "2025-08-28T13:58:47.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082, upload-time = "2025-08-28T13:58:49.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490, upload-time = "2025-08-28T13:58:51.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928, upload-time = "2025-08-28T13:58:53.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513, upload-time = "2025-08-28T13:58:55.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154, upload-time = "2025-08-28T13:58:58.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653, upload-time = "2025-08-28T13:59:00.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270, upload-time = "2025-08-28T13:59:02.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600, upload-time = "2025-08-28T13:59:04.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290, upload-time = "2025-08-28T13:59:06.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/fe/6f87b419dbe166fd30a991390221f14c5b68946f389ea07913e1719741e0/ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004", size = 12187826, upload-time = "2025-09-10T16:24:39.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/25/c92296b1fc36d2499e12b74a3fdb230f77af7bdf048fad7b0a62e94ed56a/ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9", size = 12933428, upload-time = "2025-09-10T16:24:43.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/cf/40bc7221a949470307d9c35b4ef5810c294e6cfa3caafb57d882731a9f42/ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3", size = 12095543, upload-time = "2025-09-10T16:24:46.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/03/8b5ff2a211efb68c63a1d03d157e924997ada87d01bebffbd13a0f3fcdeb/ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8", size = 12312489, upload-time = "2025-09-10T16:24:49.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/fc/2336ef6d5e9c8d8ea8305c5f91e767d795cd4fc171a6d97ef38a5302dadc/ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207", size = 11991631, upload-time = "2025-09-10T16:24:53.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/7f/f6d574d100fca83d32637d7f5541bea2f5e473c40020bbc7fc4a4d5b7294/ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24", size = 13720602, upload-time = "2025-09-10T16:24:56.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/c8/a8a5b81d8729b5d1f663348d11e2a9d65a7a9bd3c399763b1a51c72be1ce/ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea", size = 14697751, upload-time = "2025-09-10T16:24:59.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/f5/183ec292272ce7ec5e882aea74937f7288e88ecb500198b832c24debc6d3/ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2", size = 14095317, upload-time = "2025-09-10T16:25:03.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/8d/7f9771c971724701af7926c14dab31754e7b303d127b0d3f01116faef456/ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153", size = 13144418, upload-time = "2025-09-10T16:25:06.272Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/a6/7985ad1778e60922d4bef546688cd8a25822c58873e9ff30189cfe5dc4ab/ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991", size = 13370843, upload-time = "2025-09-10T16:25:09.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/1c/bafdd5a7a05a50cc51d9f5711da704942d8dd62df3d8c70c311e98ce9f8a/ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf", size = 13321891, upload-time = "2025-09-10T16:25:12.969Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/3e/7817f989cb9725ef7e8d2cee74186bf90555279e119de50c750c4b7a72fe/ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b", size = 12119119, upload-time = "2025-09-10T16:25:16.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/07/9df080742e8d1080e60c426dce6e96a8faf9a371e2ce22eef662e3839c95/ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41", size = 11961594, upload-time = "2025-09-10T16:25:19.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/f4/ae1185349197d26a2316840cb4d6c3fba61d4ac36ed728bf0228b222d71f/ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945", size = 12933377, upload-time = "2025-09-10T16:25:22.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/39/e776c10a3b349fc8209a905bfb327831d7516f6058339a613a8d2aaecacd/ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823", size = 13418555, upload-time = "2025-09-10T16:25:25.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/09/dca8df3d48e8b3f4202bf20b1658898e74b6442ac835bfe2c1816d926697/ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768", size = 12141613, upload-time = "2025-09-10T16:25:28.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/21/0647eb71ed99b888ad50e44d8ec65d7148babc0e242d531a499a0bbcda5f/ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb", size = 13258250, upload-time = "2025-09-10T16:25:31.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/a3/03216a6a86c706df54422612981fb0f9041dbb452c3401501d4a22b942c9/ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e", size = 12312357, upload-time = "2025-09-10T16:25:35.595Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2876,27 +2877,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tokenizers"
|
||||
version = "0.21.4"
|
||||
version = "0.22.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huggingface-hub" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/b4/c1ce3699e81977da2ace8b16d2badfd42b060e7d33d75c4ccdbf9dc920fa/tokenizers-0.22.0.tar.gz", hash = "sha256:2e33b98525be8453f355927f3cab312c36cd3e44f4d7e9e97da2fa94d0a49dcb", size = 362771, upload-time = "2025-08-29T10:25:33.914Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/b1/18c13648edabbe66baa85fe266a478a7931ddc0cd1ba618802eb7b8d9865/tokenizers-0.22.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:eaa9620122a3fb99b943f864af95ed14c8dfc0f47afa3b404ac8c16b3f2bb484", size = 3081954, upload-time = "2025-08-29T10:25:24.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/02/c3c454b641bd7c4f79e4464accfae9e7dfc913a777d2e561e168ae060362/tokenizers-0.22.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:71784b9ab5bf0ff3075bceeb198149d2c5e068549c0d18fe32d06ba0deb63f79", size = 2945644, upload-time = "2025-08-29T10:25:23.405Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/02/d10185ba2fd8c2d111e124c9d92de398aee0264b35ce433f79fb8472f5d0/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec5b71f668a8076802b0241a42387d48289f25435b86b769ae1837cad4172a17", size = 3254764, upload-time = "2025-08-29T10:25:12.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/89/17514bd7ef4bf5bfff58e2b131cec0f8d5cea2b1c8ffe1050a2c8de88dbb/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea8562fa7498850d02a16178105b58803ea825b50dc9094d60549a7ed63654bb", size = 3161654, upload-time = "2025-08-29T10:25:15.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/d8/bac9f3a7ef6dcceec206e3857c3b61bb16c6b702ed7ae49585f5bd85c0ef/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4136e1558a9ef2e2f1de1555dcd573e1cbc4a320c1a06c4107a3d46dc8ac6e4b", size = 3511484, upload-time = "2025-08-29T10:25:20.477Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/27/9c9800eb6763683010a4851db4d1802d8cab9cec114c17056eccb4d4a6e0/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf5954de3962a5fd9781dc12048d24a1a6f1f5df038c6e95db328cd22964206", size = 3712829, upload-time = "2025-08-29T10:25:17.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/e3/b1726dbc1f03f757260fa21752e1921445b5bc350389a8314dd3338836db/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8337ca75d0731fc4860e6204cc24bb36a67d9736142aa06ed320943b50b1e7ed", size = 3408934, upload-time = "2025-08-29T10:25:18.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/61/aeab3402c26874b74bb67a7f2c4b569dde29b51032c5384db592e7b216f4/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a89264e26f63c449d8cded9061adea7b5de53ba2346fc7e87311f7e4117c1cc8", size = 3345585, upload-time = "2025-08-29T10:25:22.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/d3/498b4a8a8764cce0900af1add0f176ff24f475d4413d55b760b8cdf00893/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:790bad50a1b59d4c21592f9c3cf5e5cf9c3c7ce7e1a23a739f13e01fb1be377a", size = 9322986, upload-time = "2025-08-29T10:25:26.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/62/92378eb1c2c565837ca3cb5f9569860d132ab9d195d7950c1ea2681dffd0/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:76cf6757c73a10ef10bf06fa937c0ec7393d90432f543f49adc8cab3fb6f26cb", size = 9276630, upload-time = "2025-08-29T10:25:28.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/f0/342d80457aa1cda7654327460f69db0d69405af1e4c453f4dc6ca7c4a76e/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1626cb186e143720c62c6c6b5371e62bbc10af60481388c0da89bc903f37ea0c", size = 9547175, upload-time = "2025-08-29T10:25:29.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/84/8aa9b4adfc4fbd09381e20a5bc6aa27040c9c09caa89988c01544e008d18/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da589a61cbfea18ae267723d6b029b84598dc8ca78db9951d8f5beff72d8507c", size = 9692735, upload-time = "2025-08-29T10:25:32.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/24/83ee2b1dc76bfe05c3142e7d0ccdfe69f0ad2f1ebf6c726cea7f0874c0d0/tokenizers-0.22.0-cp39-abi3-win32.whl", hash = "sha256:dbf9d6851bddae3e046fedfb166f47743c1c7bd11c640f0691dd35ef0bcad3be", size = 2471915, upload-time = "2025-08-29T10:25:36.411Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/9b/0e0bf82214ee20231845b127aa4a8015936ad5a46779f30865d10e404167/tokenizers-0.22.0-cp39-abi3-win_amd64.whl", hash = "sha256:c78174859eeaee96021f248a56c801e36bfb6bd5b067f2e95aa82445ca324f00", size = 2680494, upload-time = "2025-08-29T10:25:35.14Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
1
mobile/drift_schemas/main/drift_schema_v10.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v10.json
generated
Normal file
File diff suppressed because one or more lines are too long
@@ -76,7 +76,8 @@ enum StoreKey<T> {
|
||||
betaTimeline<bool>._(1002),
|
||||
enableBackup<bool>._(1003),
|
||||
useWifiForUploadVideos<bool>._(1004),
|
||||
useWifiForUploadPhotos<bool>._(1005);
|
||||
useWifiForUploadPhotos<bool>._(1005),
|
||||
needBetaMigration<bool>._(1006);
|
||||
|
||||
const StoreKey._(this.id);
|
||||
final int id;
|
||||
|
||||
@@ -1,7 +1,36 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
enum AvatarColor {
|
||||
// do not change this order or reuse indices for other purposes, adding is OK
|
||||
primary("primary"),
|
||||
pink("pink"),
|
||||
red("red"),
|
||||
yellow("yellow"),
|
||||
blue("blue"),
|
||||
green("green"),
|
||||
purple("purple"),
|
||||
orange("orange"),
|
||||
gray("gray"),
|
||||
amber("amber");
|
||||
|
||||
final String value;
|
||||
const AvatarColor(this.value);
|
||||
|
||||
Color toColor({bool isDarkTheme = false}) => switch (this) {
|
||||
AvatarColor.primary => isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
||||
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
|
||||
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
|
||||
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
|
||||
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
|
||||
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
|
||||
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
|
||||
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
|
||||
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
|
||||
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Rename to User once Isar is removed
|
||||
class UserDto {
|
||||
@@ -9,7 +38,7 @@ class UserDto {
|
||||
final String email;
|
||||
final String name;
|
||||
final bool isAdmin;
|
||||
final DateTime updatedAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
final AvatarColor avatarColor;
|
||||
|
||||
@@ -31,8 +60,8 @@ class UserDto {
|
||||
required this.id,
|
||||
required this.email,
|
||||
required this.name,
|
||||
required this.isAdmin,
|
||||
required this.updatedAt,
|
||||
this.isAdmin = false,
|
||||
this.updatedAt,
|
||||
required this.profileChangedAt,
|
||||
this.avatarColor = AvatarColor.primary,
|
||||
this.memoryEnabled = true,
|
||||
@@ -99,7 +128,8 @@ profileChangedAt: $profileChangedAt
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.id == id &&
|
||||
other.updatedAt.isAtSameMomentAs(updatedAt) &&
|
||||
((updatedAt == null && other.updatedAt == null) ||
|
||||
(updatedAt != null && other.updatedAt != null && other.updatedAt!.isAtSameMomentAs(updatedAt!))) &&
|
||||
other.avatarColor == avatarColor &&
|
||||
other.email == email &&
|
||||
other.name == name &&
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'dart:ui';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
|
||||
enum UserMetadataKey {
|
||||
// do not change this order!
|
||||
@@ -7,36 +7,6 @@ enum UserMetadataKey {
|
||||
license,
|
||||
}
|
||||
|
||||
enum AvatarColor {
|
||||
// do not change this order or reuse indices for other purposes, adding is OK
|
||||
primary("primary"),
|
||||
pink("pink"),
|
||||
red("red"),
|
||||
yellow("yellow"),
|
||||
blue("blue"),
|
||||
green("green"),
|
||||
purple("purple"),
|
||||
orange("orange"),
|
||||
gray("gray"),
|
||||
amber("amber");
|
||||
|
||||
final String value;
|
||||
const AvatarColor(this.value);
|
||||
|
||||
Color toColor({bool isDarkTheme = false}) => switch (this) {
|
||||
AvatarColor.primary => isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
||||
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
|
||||
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
|
||||
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
|
||||
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
|
||||
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
|
||||
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
|
||||
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
|
||||
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
|
||||
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
|
||||
};
|
||||
}
|
||||
|
||||
class Onboarding {
|
||||
final bool isOnboarded;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
@@ -27,6 +28,14 @@ class AssetService {
|
||||
return asset is LocalAsset ? _localAssetRepository.watch(id) : _remoteAssetRepository.watch(id);
|
||||
}
|
||||
|
||||
Future<List<LocalAsset?>> getLocalAssetsByChecksum(String checksum) {
|
||||
return _localAssetRepository.getByChecksum(checksum);
|
||||
}
|
||||
|
||||
Future<RemoteAsset?> getRemoteAssetByChecksum(String checksum) {
|
||||
return _remoteAssetRepository.getByChecksum(checksum);
|
||||
}
|
||||
|
||||
Future<RemoteAsset?> getRemoteAsset(String id) {
|
||||
return _remoteAssetRepository.get(id);
|
||||
}
|
||||
@@ -89,4 +98,8 @@ class AssetService {
|
||||
Future<int> getLocalHashedCount() {
|
||||
return _localAssetRepository.getHashedCount();
|
||||
}
|
||||
|
||||
Future<List<LocalAlbum>> getSourceAlbums(String localAssetId, {BackupSelection? backupSelection}) {
|
||||
return _localAssetRepository.getSourceAlbums(localAssetId, backupSelection: backupSelection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/utils/isolate_lock_manager.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||
@@ -24,6 +23,7 @@ import 'package:immich_mobile/utils/bootstrap.dart';
|
||||
import 'package:immich_mobile/utils/http_ssl_options.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:worker_manager/worker_manager.dart';
|
||||
|
||||
class BackgroundWorkerFgService {
|
||||
final BackgroundWorkerFgHostApi _foregroundHostApi;
|
||||
@@ -42,8 +42,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
final Drift _drift;
|
||||
final DriftLogger _driftLogger;
|
||||
final BackgroundWorkerBgHostApi _backgroundHostApi;
|
||||
final Logger _logger = Logger('BackgroundUploadBgService');
|
||||
late final IsolateLockManager _lockManager;
|
||||
final Logger _logger = Logger('BackgroundWorkerBgService');
|
||||
|
||||
bool _isCleanedUp = false;
|
||||
|
||||
@@ -59,7 +58,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
driftProvider.overrideWith(driftOverride(drift)),
|
||||
],
|
||||
);
|
||||
_lockManager = IsolateLockManager(onCloseRequest: _cleanup);
|
||||
BackgroundWorkerFlutterApi.setUp(this);
|
||||
}
|
||||
|
||||
@@ -67,41 +65,30 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
|
||||
Future<void> init() async {
|
||||
try {
|
||||
await loadTranslations();
|
||||
HttpSSLOptions.apply(applyNative: false);
|
||||
await _ref.read(authServiceProvider).setOpenApiServiceEndpoint();
|
||||
|
||||
// Initialize the file downloader
|
||||
await FileDownloader().configure(
|
||||
globalConfig: [
|
||||
// maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3
|
||||
(Config.holdingQueue, (6, 6, 3)),
|
||||
// On Android, if files are larger than 256MB, run in foreground service
|
||||
(Config.runInForegroundIfFileLargerThan, 256),
|
||||
],
|
||||
);
|
||||
await FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false);
|
||||
await FileDownloader().trackTasks();
|
||||
await Future.wait([
|
||||
loadTranslations(),
|
||||
workerManager.init(dynamicSpawning: true),
|
||||
_ref.read(authServiceProvider).setOpenApiServiceEndpoint(),
|
||||
// Initialize the file downloader
|
||||
FileDownloader().configure(
|
||||
globalConfig: [
|
||||
// maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3
|
||||
(Config.holdingQueue, (6, 6, 3)),
|
||||
// On Android, if files are larger than 256MB, run in foreground service
|
||||
(Config.runInForegroundIfFileLargerThan, 256),
|
||||
],
|
||||
),
|
||||
FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false),
|
||||
FileDownloader().trackTasks(),
|
||||
_ref.read(fileMediaRepositoryProvider).enableBackgroundAccess(),
|
||||
]);
|
||||
|
||||
configureFileDownloaderNotifications();
|
||||
await _ref.read(fileMediaRepositoryProvider).enableBackgroundAccess();
|
||||
|
||||
// Notify the host that the background upload service has been initialized and is ready to use
|
||||
debugPrint("Acquiring background worker lock");
|
||||
if (await _lockManager.acquireLock().timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
_lockManager.cancel();
|
||||
return false;
|
||||
},
|
||||
)) {
|
||||
_logger.info("Acquired background worker lock");
|
||||
await _backgroundHostApi.onInitialized();
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.warning("Failed to acquire background worker lock");
|
||||
await _cleanup();
|
||||
await _backgroundHostApi.close();
|
||||
// Notify the host that the background worker service has been initialized and is ready to use
|
||||
_backgroundHostApi.onInitialized();
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to initialize background worker", error, stack);
|
||||
_backgroundHostApi.close();
|
||||
@@ -169,15 +156,19 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
try {
|
||||
_isCleanedUp = true;
|
||||
_logger.info("Cleaning up background worker");
|
||||
await _ref.read(backgroundSyncProvider).cancel();
|
||||
await _ref.read(backgroundSyncProvider).cancelLocal();
|
||||
final cleanupFutures = [
|
||||
workerManager.dispose(),
|
||||
_drift.close(),
|
||||
_driftLogger.close(),
|
||||
_ref.read(backgroundSyncProvider).cancel(),
|
||||
_ref.read(backgroundSyncProvider).cancelLocal(),
|
||||
];
|
||||
|
||||
if (_isar.isOpen) {
|
||||
await _isar.close();
|
||||
cleanupFutures.add(_isar.close());
|
||||
}
|
||||
await _drift.close();
|
||||
await _driftLogger.close();
|
||||
_ref.dispose();
|
||||
_lockManager.releaseLock();
|
||||
await Future.wait(cleanupFutures);
|
||||
_logger.info("Background worker resources cleaned up");
|
||||
} catch (error, stack) {
|
||||
debugPrint('Failed to cleanup background worker: $error with stack: $stack');
|
||||
@@ -186,22 +177,29 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
|
||||
Future<void> _handleBackup({bool processBulk = true}) async {
|
||||
if (!_isBackupEnabled) {
|
||||
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.info("[_handleBackup 2] Enqueuing assets for backup from the background service");
|
||||
|
||||
final currentUser = _ref.read(currentUserProvider);
|
||||
if (currentUser == null) {
|
||||
_logger.warning("[_handleBackup 3] No current user found. Skipping backup from background");
|
||||
return;
|
||||
}
|
||||
|
||||
if (processBulk) {
|
||||
_logger.info("[_handleBackup 4] Resume backup from background");
|
||||
return _ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
|
||||
}
|
||||
|
||||
final activeTask = await _ref.read(uploadServiceProvider).getActiveTasks(currentUser.id);
|
||||
if (activeTask.isNotEmpty) {
|
||||
_logger.info("[_handleBackup 5] Resuming backup for active tasks from background");
|
||||
await _ref.read(uploadServiceProvider).resumeBackup();
|
||||
} else {
|
||||
_logger.info("[_handleBackup 6] Starting serial backup for new tasks from background");
|
||||
await _ref.read(uploadServiceProvider).startBackupSerial(currentUser.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class StoreService {
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
bool get isBetaTimelineEnabled => tryGet(StoreKey.betaTimeline) ?? false;
|
||||
bool get isBetaTimelineEnabled => tryGet(StoreKey.betaTimeline) ?? true;
|
||||
}
|
||||
|
||||
class StoreKeyNotFoundException implements Exception {
|
||||
|
||||
@@ -23,54 +23,17 @@ class SyncStreamService {
|
||||
|
||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||
|
||||
Future<void> sync() {
|
||||
Future<void> sync() async {
|
||||
_logger.info("Remote sync request for user");
|
||||
// Start the sync stream and handle events
|
||||
return _syncApiRepository.streamChanges(_handleEvents);
|
||||
}
|
||||
|
||||
Future<void> handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) async {
|
||||
if (batchData.isEmpty) return;
|
||||
|
||||
_logger.info('Processing batch of ${batchData.length} AssetUploadReadyV1 events');
|
||||
|
||||
final List<SyncAssetV1> assets = [];
|
||||
final List<SyncAssetExifV1> exifs = [];
|
||||
|
||||
try {
|
||||
for (final data in batchData) {
|
||||
if (data is! Map<String, dynamic>) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final payload = data;
|
||||
final assetData = payload['asset'];
|
||||
final exifData = payload['exif'];
|
||||
|
||||
if (assetData == null || exifData == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final asset = SyncAssetV1.fromJson(assetData);
|
||||
final exif = SyncAssetExifV1.fromJson(exifData);
|
||||
|
||||
if (asset != null && exif != null) {
|
||||
assets.add(asset);
|
||||
exifs.add(exif);
|
||||
}
|
||||
}
|
||||
|
||||
if (assets.isNotEmpty && exifs.isNotEmpty) {
|
||||
await _syncStreamRepository.updateAssetsV1(assets, debugLabel: 'websocket-batch');
|
||||
await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
|
||||
_logger.info('Successfully processed ${assets.length} assets in batch');
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe("Error processing AssetUploadReadyV1 websocket batch events", error, stackTrace);
|
||||
bool shouldReset = false;
|
||||
await _syncApiRepository.streamChanges(_handleEvents, onReset: () => shouldReset = true);
|
||||
if (shouldReset) {
|
||||
await _syncApiRepository.streamChanges(_handleEvents);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleEvents(List<SyncEvent> events, Function() abort) async {
|
||||
Future<void> _handleEvents(List<SyncEvent> events, Function() abort, Function() reset) async {
|
||||
List<SyncEvent> items = [];
|
||||
for (final event in events) {
|
||||
if (isCancelled) {
|
||||
@@ -83,6 +46,10 @@ class SyncStreamService {
|
||||
await _processBatch(items);
|
||||
}
|
||||
|
||||
if (event.type == SyncEntityType.syncResetV1) {
|
||||
reset();
|
||||
}
|
||||
|
||||
items.add(event);
|
||||
}
|
||||
|
||||
@@ -103,6 +70,8 @@ class SyncStreamService {
|
||||
Future<void> _handleSyncData(SyncEntityType type, Iterable<Object> data) async {
|
||||
_logger.fine("Processing sync data for $type of length ${data.length}");
|
||||
switch (type) {
|
||||
case SyncEntityType.authUserV1:
|
||||
return _syncStreamRepository.updateAuthUsersV1(data.cast());
|
||||
case SyncEntityType.userV1:
|
||||
return _syncStreamRepository.updateUsersV1(data.cast());
|
||||
case SyncEntityType.userDeleteV1:
|
||||
@@ -159,6 +128,12 @@ class SyncStreamService {
|
||||
// to acknowledge that the client has processed all the backfill events
|
||||
case SyncEntityType.syncAckV1:
|
||||
return;
|
||||
// No-op. SyncCompleteV1 is used to signal the completion of the sync process
|
||||
case SyncEntityType.syncCompleteV1:
|
||||
return;
|
||||
// Request to reset the client state. Clear everything related to remote entities
|
||||
case SyncEntityType.syncResetV1:
|
||||
return _syncStreamRepository.reset();
|
||||
case SyncEntityType.memoryV1:
|
||||
return _syncStreamRepository.updateMemoriesV1(data.cast());
|
||||
case SyncEntityType.memoryDeleteV1:
|
||||
@@ -193,4 +168,45 @@ class SyncStreamService {
|
||||
_logger.warning("Unknown sync data type: $type");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) async {
|
||||
if (batchData.isEmpty) return;
|
||||
|
||||
_logger.info('Processing batch of ${batchData.length} AssetUploadReadyV1 events');
|
||||
|
||||
final List<SyncAssetV1> assets = [];
|
||||
final List<SyncAssetExifV1> exifs = [];
|
||||
|
||||
try {
|
||||
for (final data in batchData) {
|
||||
if (data is! Map<String, dynamic>) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final payload = data;
|
||||
final assetData = payload['asset'];
|
||||
final exifData = payload['exif'];
|
||||
|
||||
if (assetData == null || exifData == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final asset = SyncAssetV1.fromJson(assetData);
|
||||
final exif = SyncAssetExifV1.fromJson(exifData);
|
||||
|
||||
if (asset != null && exif != null) {
|
||||
assets.add(asset);
|
||||
exifs.add(exif);
|
||||
}
|
||||
}
|
||||
|
||||
if (assets.isNotEmpty && exifs.isNotEmpty) {
|
||||
await _syncStreamRepository.updateAssetsV1(assets, debugLabel: 'websocket-batch');
|
||||
await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
|
||||
_logger.info('Successfully processed ${assets.length} assets in batch');
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe("Error processing AssetUploadReadyV1 websocket batch events", error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
const String kIsolateLockManagerPort = "immich://isolate_mutex";
|
||||
|
||||
enum _LockStatus { active, released }
|
||||
|
||||
class _IsolateRequest {
|
||||
const _IsolateRequest();
|
||||
}
|
||||
|
||||
class _HeartbeatRequest extends _IsolateRequest {
|
||||
// Port for the receiver to send replies back
|
||||
final SendPort sendPort;
|
||||
|
||||
const _HeartbeatRequest(this.sendPort);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'type': 'heartbeat', 'sendPort': sendPort};
|
||||
}
|
||||
}
|
||||
|
||||
class _CloseRequest extends _IsolateRequest {
|
||||
const _CloseRequest();
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'type': 'close'};
|
||||
}
|
||||
}
|
||||
|
||||
class _IsolateResponse {
|
||||
const _IsolateResponse();
|
||||
}
|
||||
|
||||
class _HeartbeatResponse extends _IsolateResponse {
|
||||
final _LockStatus status;
|
||||
|
||||
const _HeartbeatResponse(this.status);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'type': 'heartbeat', 'status': status.index};
|
||||
}
|
||||
}
|
||||
|
||||
typedef OnCloseLockHolderRequest = void Function();
|
||||
|
||||
class IsolateLockManager {
|
||||
final String _portName;
|
||||
bool _hasLock = false;
|
||||
ReceivePort? _receivePort;
|
||||
final OnCloseLockHolderRequest? _onCloseRequest;
|
||||
final Set<SendPort> _waitingIsolates = {};
|
||||
// Token object - a new one is created for each acquisition attempt
|
||||
Object? _currentAcquisitionToken;
|
||||
|
||||
IsolateLockManager({String? portName, OnCloseLockHolderRequest? onCloseRequest})
|
||||
: _portName = portName ?? kIsolateLockManagerPort,
|
||||
_onCloseRequest = onCloseRequest;
|
||||
|
||||
Future<bool> acquireLock() async {
|
||||
if (_hasLock) {
|
||||
Logger('BackgroundWorkerLockManager').warning("WARNING: [acquireLock] called more than once");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create a new token - this invalidates any previous attempt
|
||||
final token = _currentAcquisitionToken = Object();
|
||||
|
||||
final ReceivePort rp = _receivePort = ReceivePort(_portName);
|
||||
final SendPort sp = rp.sendPort;
|
||||
|
||||
while (!IsolateNameServer.registerPortWithName(sp, _portName)) {
|
||||
// This attempt was superseded by a newer one in the same isolate
|
||||
if (_currentAcquisitionToken != token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await _lockReleasedByHolder(token);
|
||||
}
|
||||
|
||||
_hasLock = true;
|
||||
rp.listen(_onRequest);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _lockReleasedByHolder(Object token) async {
|
||||
SendPort? holder = IsolateNameServer.lookupPortByName(_portName);
|
||||
debugPrint("Found lock holder: $holder");
|
||||
if (holder == null) {
|
||||
// No holder, try and acquire lock
|
||||
return;
|
||||
}
|
||||
|
||||
final ReceivePort tempRp = ReceivePort();
|
||||
final SendPort tempSp = tempRp.sendPort;
|
||||
final bs = tempRp.asBroadcastStream();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
// Send a heartbeat request with the send port to receive reply from the holder
|
||||
|
||||
debugPrint("Sending heartbeat request to lock holder");
|
||||
holder.send(_HeartbeatRequest(tempSp).toJson());
|
||||
dynamic answer = await bs.first.timeout(const Duration(seconds: 3), onTimeout: () => null);
|
||||
|
||||
debugPrint("Received heartbeat response from lock holder: $answer");
|
||||
// This attempt was superseded by a newer one in the same isolate
|
||||
if (_currentAcquisitionToken != token) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (answer == null) {
|
||||
// Holder failed, most likely killed without calling releaseLock
|
||||
// Check if a different waiting isolate took the lock
|
||||
if (holder == IsolateNameServer.lookupPortByName(_portName)) {
|
||||
// No, remove the stale lock
|
||||
IsolateNameServer.removePortNameMapping(_portName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Unknown message type received for heartbeat request. Try again
|
||||
_IsolateResponse? response = _parseResponse(answer);
|
||||
if (response == null || response is! _HeartbeatResponse) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (response.status == _LockStatus.released) {
|
||||
// Holder has released the lock
|
||||
break;
|
||||
}
|
||||
|
||||
// If the _LockStatus is active, we check again if the task completed
|
||||
// by sending a released messaged again, if not, send a new heartbeat again
|
||||
|
||||
// Check if the holder completed its task after the heartbeat
|
||||
answer = await bs.first.timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () => const _HeartbeatResponse(_LockStatus.active).toJson(),
|
||||
);
|
||||
|
||||
response = _parseResponse(answer);
|
||||
if (response is _HeartbeatResponse && response.status == _LockStatus.released) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Timeout or error
|
||||
} finally {
|
||||
tempRp.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_IsolateRequest? _parseRequest(dynamic msg) {
|
||||
if (msg is! Map<String, dynamic>) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (msg['type']) {
|
||||
'heartbeat' => _HeartbeatRequest(msg['sendPort']),
|
||||
'close' => const _CloseRequest(),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
_IsolateResponse? _parseResponse(dynamic msg) {
|
||||
if (msg is! Map<String, dynamic>) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (msg['type']) {
|
||||
'heartbeat' => _HeartbeatResponse(_LockStatus.values[msg['status']]),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
// Executed in the isolate with the lock
|
||||
void _onRequest(dynamic msg) {
|
||||
final request = _parseRequest(msg);
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (request is _HeartbeatRequest) {
|
||||
// Add the send port to the list of waiting isolates
|
||||
_waitingIsolates.add(request.sendPort);
|
||||
request.sendPort.send(const _HeartbeatResponse(_LockStatus.active).toJson());
|
||||
return;
|
||||
}
|
||||
|
||||
if (request is _CloseRequest) {
|
||||
_onCloseRequest?.call();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void releaseLock() {
|
||||
if (_hasLock) {
|
||||
IsolateNameServer.removePortNameMapping(_portName);
|
||||
|
||||
// Notify waiting isolates
|
||||
for (final port in _waitingIsolates) {
|
||||
port.send(const _HeartbeatResponse(_LockStatus.released).toJson());
|
||||
}
|
||||
_waitingIsolates.clear();
|
||||
|
||||
_hasLock = false;
|
||||
}
|
||||
|
||||
_receivePort?.close();
|
||||
_receivePort = null;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (_hasLock) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint("Cancelling ongoing acquire lock attempts");
|
||||
// Create a new token to invalidate ongoing acquire lock attempts
|
||||
_currentAcquisitionToken = Object();
|
||||
}
|
||||
|
||||
void requestHolderToClose() {
|
||||
if (_hasLock) {
|
||||
return;
|
||||
}
|
||||
|
||||
IsolateNameServer.lookupPortByName(_portName)?.send(const _CloseRequest().toJson());
|
||||
}
|
||||
}
|
||||
27
mobile/lib/infrastructure/entities/auth_user.entity.dart
Normal file
27
mobile/lib/infrastructure/entities/auth_user.entity.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
class AuthUserEntity extends Table with DriftDefaultsMixin {
|
||||
const AuthUserEntity();
|
||||
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get email => text()();
|
||||
BoolColumn get isAdmin => boolean().withDefault(const Constant(false))();
|
||||
|
||||
// Profile image
|
||||
BoolColumn get hasProfileImage => boolean().withDefault(const Constant(false))();
|
||||
DateTimeColumn get profileChangedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
IntColumn get avatarColor => intEnum<AvatarColor>()();
|
||||
|
||||
// Quota
|
||||
IntColumn get quotaSizeInBytes => integer().withDefault(const Constant(0))();
|
||||
IntColumn get quotaUsageInBytes => integer().withDefault(const Constant(0))();
|
||||
|
||||
// Locked Folder
|
||||
TextColumn get pinCode => text().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
933
mobile/lib/infrastructure/entities/auth_user.entity.drift.dart
generated
Normal file
933
mobile/lib/infrastructure/entities/auth_user.entity.drift.dart
generated
Normal file
@@ -0,0 +1,933 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'
|
||||
as i1;
|
||||
import 'package:immich_mobile/domain/models/user.model.dart' as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart'
|
||||
as i3;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||
|
||||
typedef $$AuthUserEntityTableCreateCompanionBuilder =
|
||||
i1.AuthUserEntityCompanion Function({
|
||||
required String id,
|
||||
required String name,
|
||||
required String email,
|
||||
i0.Value<bool> isAdmin,
|
||||
i0.Value<bool> hasProfileImage,
|
||||
i0.Value<DateTime> profileChangedAt,
|
||||
required i2.AvatarColor avatarColor,
|
||||
i0.Value<int> quotaSizeInBytes,
|
||||
i0.Value<int> quotaUsageInBytes,
|
||||
i0.Value<String?> pinCode,
|
||||
});
|
||||
typedef $$AuthUserEntityTableUpdateCompanionBuilder =
|
||||
i1.AuthUserEntityCompanion Function({
|
||||
i0.Value<String> id,
|
||||
i0.Value<String> name,
|
||||
i0.Value<String> email,
|
||||
i0.Value<bool> isAdmin,
|
||||
i0.Value<bool> hasProfileImage,
|
||||
i0.Value<DateTime> profileChangedAt,
|
||||
i0.Value<i2.AvatarColor> avatarColor,
|
||||
i0.Value<int> quotaSizeInBytes,
|
||||
i0.Value<int> quotaUsageInBytes,
|
||||
i0.Value<String?> pinCode,
|
||||
});
|
||||
|
||||
class $$AuthUserEntityTableFilterComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$AuthUserEntityTable> {
|
||||
$$AuthUserEntityTableFilterComposer({
|
||||
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<String> get name => $composableBuilder(
|
||||
column: $table.name,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<String> get email => $composableBuilder(
|
||||
column: $table.email,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<bool> get isAdmin => $composableBuilder(
|
||||
column: $table.isAdmin,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<bool> get hasProfileImage => $composableBuilder(
|
||||
column: $table.hasProfileImage,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<DateTime> get profileChangedAt => $composableBuilder(
|
||||
column: $table.profileChangedAt,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnWithTypeConverterFilters<i2.AvatarColor, i2.AvatarColor, int>
|
||||
get avatarColor => $composableBuilder(
|
||||
column: $table.avatarColor,
|
||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<int> get quotaSizeInBytes => $composableBuilder(
|
||||
column: $table.quotaSizeInBytes,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<int> get quotaUsageInBytes => $composableBuilder(
|
||||
column: $table.quotaUsageInBytes,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<String> get pinCode => $composableBuilder(
|
||||
column: $table.pinCode,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$AuthUserEntityTableOrderingComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$AuthUserEntityTable> {
|
||||
$$AuthUserEntityTableOrderingComposer({
|
||||
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<String> get name => $composableBuilder(
|
||||
column: $table.name,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<String> get email => $composableBuilder(
|
||||
column: $table.email,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<bool> get isAdmin => $composableBuilder(
|
||||
column: $table.isAdmin,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<bool> get hasProfileImage => $composableBuilder(
|
||||
column: $table.hasProfileImage,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<DateTime> get profileChangedAt => $composableBuilder(
|
||||
column: $table.profileChangedAt,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<int> get avatarColor => $composableBuilder(
|
||||
column: $table.avatarColor,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<int> get quotaSizeInBytes => $composableBuilder(
|
||||
column: $table.quotaSizeInBytes,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<int> get quotaUsageInBytes => $composableBuilder(
|
||||
column: $table.quotaUsageInBytes,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<String> get pinCode => $composableBuilder(
|
||||
column: $table.pinCode,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$AuthUserEntityTableAnnotationComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$AuthUserEntityTable> {
|
||||
$$AuthUserEntityTableAnnotationComposer({
|
||||
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<String> get name =>
|
||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get email =>
|
||||
$composableBuilder(column: $table.email, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<bool> get isAdmin =>
|
||||
$composableBuilder(column: $table.isAdmin, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<bool> get hasProfileImage => $composableBuilder(
|
||||
column: $table.hasProfileImage,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<DateTime> get profileChangedAt => $composableBuilder(
|
||||
column: $table.profileChangedAt,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumnWithTypeConverter<i2.AvatarColor, int> get avatarColor =>
|
||||
$composableBuilder(
|
||||
column: $table.avatarColor,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<int> get quotaSizeInBytes => $composableBuilder(
|
||||
column: $table.quotaSizeInBytes,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<int> get quotaUsageInBytes => $composableBuilder(
|
||||
column: $table.quotaUsageInBytes,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<String> get pinCode =>
|
||||
$composableBuilder(column: $table.pinCode, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $$AuthUserEntityTableTableManager
|
||||
extends
|
||||
i0.RootTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$AuthUserEntityTable,
|
||||
i1.AuthUserEntityData,
|
||||
i1.$$AuthUserEntityTableFilterComposer,
|
||||
i1.$$AuthUserEntityTableOrderingComposer,
|
||||
i1.$$AuthUserEntityTableAnnotationComposer,
|
||||
$$AuthUserEntityTableCreateCompanionBuilder,
|
||||
$$AuthUserEntityTableUpdateCompanionBuilder,
|
||||
(
|
||||
i1.AuthUserEntityData,
|
||||
i0.BaseReferences<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$AuthUserEntityTable,
|
||||
i1.AuthUserEntityData
|
||||
>,
|
||||
),
|
||||
i1.AuthUserEntityData,
|
||||
i0.PrefetchHooks Function()
|
||||
> {
|
||||
$$AuthUserEntityTableTableManager(
|
||||
i0.GeneratedDatabase db,
|
||||
i1.$AuthUserEntityTable table,
|
||||
) : super(
|
||||
i0.TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
i1.$$AuthUserEntityTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
i1.$$AuthUserEntityTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () => i1
|
||||
.$$AuthUserEntityTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback:
|
||||
({
|
||||
i0.Value<String> id = const i0.Value.absent(),
|
||||
i0.Value<String> name = const i0.Value.absent(),
|
||||
i0.Value<String> email = const i0.Value.absent(),
|
||||
i0.Value<bool> isAdmin = const i0.Value.absent(),
|
||||
i0.Value<bool> hasProfileImage = const i0.Value.absent(),
|
||||
i0.Value<DateTime> profileChangedAt = const i0.Value.absent(),
|
||||
i0.Value<i2.AvatarColor> avatarColor = const i0.Value.absent(),
|
||||
i0.Value<int> quotaSizeInBytes = const i0.Value.absent(),
|
||||
i0.Value<int> quotaUsageInBytes = const i0.Value.absent(),
|
||||
i0.Value<String?> pinCode = const i0.Value.absent(),
|
||||
}) => i1.AuthUserEntityCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
email: email,
|
||||
isAdmin: isAdmin,
|
||||
hasProfileImage: hasProfileImage,
|
||||
profileChangedAt: profileChangedAt,
|
||||
avatarColor: avatarColor,
|
||||
quotaSizeInBytes: quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes,
|
||||
pinCode: pinCode,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
required String id,
|
||||
required String name,
|
||||
required String email,
|
||||
i0.Value<bool> isAdmin = const i0.Value.absent(),
|
||||
i0.Value<bool> hasProfileImage = const i0.Value.absent(),
|
||||
i0.Value<DateTime> profileChangedAt = const i0.Value.absent(),
|
||||
required i2.AvatarColor avatarColor,
|
||||
i0.Value<int> quotaSizeInBytes = const i0.Value.absent(),
|
||||
i0.Value<int> quotaUsageInBytes = const i0.Value.absent(),
|
||||
i0.Value<String?> pinCode = const i0.Value.absent(),
|
||||
}) => i1.AuthUserEntityCompanion.insert(
|
||||
id: id,
|
||||
name: name,
|
||||
email: email,
|
||||
isAdmin: isAdmin,
|
||||
hasProfileImage: hasProfileImage,
|
||||
profileChangedAt: profileChangedAt,
|
||||
avatarColor: avatarColor,
|
||||
quotaSizeInBytes: quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes,
|
||||
pinCode: pinCode,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
typedef $$AuthUserEntityTableProcessedTableManager =
|
||||
i0.ProcessedTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$AuthUserEntityTable,
|
||||
i1.AuthUserEntityData,
|
||||
i1.$$AuthUserEntityTableFilterComposer,
|
||||
i1.$$AuthUserEntityTableOrderingComposer,
|
||||
i1.$$AuthUserEntityTableAnnotationComposer,
|
||||
$$AuthUserEntityTableCreateCompanionBuilder,
|
||||
$$AuthUserEntityTableUpdateCompanionBuilder,
|
||||
(
|
||||
i1.AuthUserEntityData,
|
||||
i0.BaseReferences<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$AuthUserEntityTable,
|
||||
i1.AuthUserEntityData
|
||||
>,
|
||||
),
|
||||
i1.AuthUserEntityData,
|
||||
i0.PrefetchHooks Function()
|
||||
>;
|
||||
|
||||
class $AuthUserEntityTable extends i3.AuthUserEntity
|
||||
with i0.TableInfo<$AuthUserEntityTable, i1.AuthUserEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$AuthUserEntityTable(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 _nameMeta = const i0.VerificationMeta(
|
||||
'name',
|
||||
);
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> name = i0.GeneratedColumn<String>(
|
||||
'name',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const i0.VerificationMeta _emailMeta = const i0.VerificationMeta(
|
||||
'email',
|
||||
);
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> email = i0.GeneratedColumn<String>(
|
||||
'email',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const i0.VerificationMeta _isAdminMeta = const i0.VerificationMeta(
|
||||
'isAdmin',
|
||||
);
|
||||
@override
|
||||
late final i0.GeneratedColumn<bool> isAdmin = i0.GeneratedColumn<bool>(
|
||||
'is_admin',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("is_admin" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const i4.Constant(false),
|
||||
);
|
||||
static const i0.VerificationMeta _hasProfileImageMeta =
|
||||
const i0.VerificationMeta('hasProfileImage');
|
||||
@override
|
||||
late final i0.GeneratedColumn<bool> hasProfileImage =
|
||||
i0.GeneratedColumn<bool>(
|
||||
'has_profile_image',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("has_profile_image" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const i4.Constant(false),
|
||||
);
|
||||
static const i0.VerificationMeta _profileChangedAtMeta =
|
||||
const i0.VerificationMeta('profileChangedAt');
|
||||
@override
|
||||
late final i0.GeneratedColumn<DateTime> profileChangedAt =
|
||||
i0.GeneratedColumn<DateTime>(
|
||||
'profile_changed_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: i4.currentDateAndTime,
|
||||
);
|
||||
@override
|
||||
late final i0.GeneratedColumnWithTypeConverter<i2.AvatarColor, int>
|
||||
avatarColor =
|
||||
i0.GeneratedColumn<int>(
|
||||
'avatar_color',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
).withConverter<i2.AvatarColor>(
|
||||
i1.$AuthUserEntityTable.$converteravatarColor,
|
||||
);
|
||||
static const i0.VerificationMeta _quotaSizeInBytesMeta =
|
||||
const i0.VerificationMeta('quotaSizeInBytes');
|
||||
@override
|
||||
late final i0.GeneratedColumn<int> quotaSizeInBytes = i0.GeneratedColumn<int>(
|
||||
'quota_size_in_bytes',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const i4.Constant(0),
|
||||
);
|
||||
static const i0.VerificationMeta _quotaUsageInBytesMeta =
|
||||
const i0.VerificationMeta('quotaUsageInBytes');
|
||||
@override
|
||||
late final i0.GeneratedColumn<int> quotaUsageInBytes =
|
||||
i0.GeneratedColumn<int>(
|
||||
'quota_usage_in_bytes',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const i4.Constant(0),
|
||||
);
|
||||
static const i0.VerificationMeta _pinCodeMeta = const i0.VerificationMeta(
|
||||
'pinCode',
|
||||
);
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> pinCode = i0.GeneratedColumn<String>(
|
||||
'pin_code',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
isAdmin,
|
||||
hasProfileImage,
|
||||
profileChangedAt,
|
||||
avatarColor,
|
||||
quotaSizeInBytes,
|
||||
quotaUsageInBytes,
|
||||
pinCode,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'auth_user_entity';
|
||||
@override
|
||||
i0.VerificationContext validateIntegrity(
|
||||
i0.Insertable<i1.AuthUserEntityData> 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('name')) {
|
||||
context.handle(
|
||||
_nameMeta,
|
||||
name.isAcceptableOrUnknown(data['name']!, _nameMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_nameMeta);
|
||||
}
|
||||
if (data.containsKey('email')) {
|
||||
context.handle(
|
||||
_emailMeta,
|
||||
email.isAcceptableOrUnknown(data['email']!, _emailMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_emailMeta);
|
||||
}
|
||||
if (data.containsKey('is_admin')) {
|
||||
context.handle(
|
||||
_isAdminMeta,
|
||||
isAdmin.isAcceptableOrUnknown(data['is_admin']!, _isAdminMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('has_profile_image')) {
|
||||
context.handle(
|
||||
_hasProfileImageMeta,
|
||||
hasProfileImage.isAcceptableOrUnknown(
|
||||
data['has_profile_image']!,
|
||||
_hasProfileImageMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('profile_changed_at')) {
|
||||
context.handle(
|
||||
_profileChangedAtMeta,
|
||||
profileChangedAt.isAcceptableOrUnknown(
|
||||
data['profile_changed_at']!,
|
||||
_profileChangedAtMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('quota_size_in_bytes')) {
|
||||
context.handle(
|
||||
_quotaSizeInBytesMeta,
|
||||
quotaSizeInBytes.isAcceptableOrUnknown(
|
||||
data['quota_size_in_bytes']!,
|
||||
_quotaSizeInBytesMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('quota_usage_in_bytes')) {
|
||||
context.handle(
|
||||
_quotaUsageInBytesMeta,
|
||||
quotaUsageInBytes.isAcceptableOrUnknown(
|
||||
data['quota_usage_in_bytes']!,
|
||||
_quotaUsageInBytesMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('pin_code')) {
|
||||
context.handle(
|
||||
_pinCodeMeta,
|
||||
pinCode.isAcceptableOrUnknown(data['pin_code']!, _pinCodeMeta),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<i0.GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
i1.AuthUserEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.AuthUserEntityData(
|
||||
id: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}id'],
|
||||
)!,
|
||||
name: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}name'],
|
||||
)!,
|
||||
email: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}email'],
|
||||
)!,
|
||||
isAdmin: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool,
|
||||
data['${effectivePrefix}is_admin'],
|
||||
)!,
|
||||
hasProfileImage: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool,
|
||||
data['${effectivePrefix}has_profile_image'],
|
||||
)!,
|
||||
profileChangedAt: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}profile_changed_at'],
|
||||
)!,
|
||||
avatarColor: i1.$AuthUserEntityTable.$converteravatarColor.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int,
|
||||
data['${effectivePrefix}avatar_color'],
|
||||
)!,
|
||||
),
|
||||
quotaSizeInBytes: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int,
|
||||
data['${effectivePrefix}quota_size_in_bytes'],
|
||||
)!,
|
||||
quotaUsageInBytes: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int,
|
||||
data['${effectivePrefix}quota_usage_in_bytes'],
|
||||
)!,
|
||||
pinCode: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}pin_code'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$AuthUserEntityTable createAlias(String alias) {
|
||||
return $AuthUserEntityTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static i0.JsonTypeConverter2<i2.AvatarColor, int, int> $converteravatarColor =
|
||||
const i0.EnumIndexConverter<i2.AvatarColor>(i2.AvatarColor.values);
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
}
|
||||
|
||||
class AuthUserEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.AuthUserEntityData> {
|
||||
final String id;
|
||||
final String name;
|
||||
final String email;
|
||||
final bool isAdmin;
|
||||
final bool hasProfileImage;
|
||||
final DateTime profileChangedAt;
|
||||
final i2.AvatarColor avatarColor;
|
||||
final int quotaSizeInBytes;
|
||||
final int quotaUsageInBytes;
|
||||
final String? pinCode;
|
||||
const AuthUserEntityData({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.email,
|
||||
required this.isAdmin,
|
||||
required this.hasProfileImage,
|
||||
required this.profileChangedAt,
|
||||
required this.avatarColor,
|
||||
required this.quotaSizeInBytes,
|
||||
required this.quotaUsageInBytes,
|
||||
this.pinCode,
|
||||
});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['id'] = i0.Variable<String>(id);
|
||||
map['name'] = i0.Variable<String>(name);
|
||||
map['email'] = i0.Variable<String>(email);
|
||||
map['is_admin'] = i0.Variable<bool>(isAdmin);
|
||||
map['has_profile_image'] = i0.Variable<bool>(hasProfileImage);
|
||||
map['profile_changed_at'] = i0.Variable<DateTime>(profileChangedAt);
|
||||
{
|
||||
map['avatar_color'] = i0.Variable<int>(
|
||||
i1.$AuthUserEntityTable.$converteravatarColor.toSql(avatarColor),
|
||||
);
|
||||
}
|
||||
map['quota_size_in_bytes'] = i0.Variable<int>(quotaSizeInBytes);
|
||||
map['quota_usage_in_bytes'] = i0.Variable<int>(quotaUsageInBytes);
|
||||
if (!nullToAbsent || pinCode != null) {
|
||||
map['pin_code'] = i0.Variable<String>(pinCode);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
factory AuthUserEntityData.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
i0.ValueSerializer? serializer,
|
||||
}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return AuthUserEntityData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
email: serializer.fromJson<String>(json['email']),
|
||||
isAdmin: serializer.fromJson<bool>(json['isAdmin']),
|
||||
hasProfileImage: serializer.fromJson<bool>(json['hasProfileImage']),
|
||||
profileChangedAt: serializer.fromJson<DateTime>(json['profileChangedAt']),
|
||||
avatarColor: i1.$AuthUserEntityTable.$converteravatarColor.fromJson(
|
||||
serializer.fromJson<int>(json['avatarColor']),
|
||||
),
|
||||
quotaSizeInBytes: serializer.fromJson<int>(json['quotaSizeInBytes']),
|
||||
quotaUsageInBytes: serializer.fromJson<int>(json['quotaUsageInBytes']),
|
||||
pinCode: serializer.fromJson<String?>(json['pinCode']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'email': serializer.toJson<String>(email),
|
||||
'isAdmin': serializer.toJson<bool>(isAdmin),
|
||||
'hasProfileImage': serializer.toJson<bool>(hasProfileImage),
|
||||
'profileChangedAt': serializer.toJson<DateTime>(profileChangedAt),
|
||||
'avatarColor': serializer.toJson<int>(
|
||||
i1.$AuthUserEntityTable.$converteravatarColor.toJson(avatarColor),
|
||||
),
|
||||
'quotaSizeInBytes': serializer.toJson<int>(quotaSizeInBytes),
|
||||
'quotaUsageInBytes': serializer.toJson<int>(quotaUsageInBytes),
|
||||
'pinCode': serializer.toJson<String?>(pinCode),
|
||||
};
|
||||
}
|
||||
|
||||
i1.AuthUserEntityData copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? email,
|
||||
bool? isAdmin,
|
||||
bool? hasProfileImage,
|
||||
DateTime? profileChangedAt,
|
||||
i2.AvatarColor? avatarColor,
|
||||
int? quotaSizeInBytes,
|
||||
int? quotaUsageInBytes,
|
||||
i0.Value<String?> pinCode = const i0.Value.absent(),
|
||||
}) => i1.AuthUserEntityData(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
email: email ?? this.email,
|
||||
isAdmin: isAdmin ?? this.isAdmin,
|
||||
hasProfileImage: hasProfileImage ?? this.hasProfileImage,
|
||||
profileChangedAt: profileChangedAt ?? this.profileChangedAt,
|
||||
avatarColor: avatarColor ?? this.avatarColor,
|
||||
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
|
||||
pinCode: pinCode.present ? pinCode.value : this.pinCode,
|
||||
);
|
||||
AuthUserEntityData copyWithCompanion(i1.AuthUserEntityCompanion data) {
|
||||
return AuthUserEntityData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
name: data.name.present ? data.name.value : this.name,
|
||||
email: data.email.present ? data.email.value : this.email,
|
||||
isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin,
|
||||
hasProfileImage: data.hasProfileImage.present
|
||||
? data.hasProfileImage.value
|
||||
: this.hasProfileImage,
|
||||
profileChangedAt: data.profileChangedAt.present
|
||||
? data.profileChangedAt.value
|
||||
: this.profileChangedAt,
|
||||
avatarColor: data.avatarColor.present
|
||||
? data.avatarColor.value
|
||||
: this.avatarColor,
|
||||
quotaSizeInBytes: data.quotaSizeInBytes.present
|
||||
? data.quotaSizeInBytes.value
|
||||
: this.quotaSizeInBytes,
|
||||
quotaUsageInBytes: data.quotaUsageInBytes.present
|
||||
? data.quotaUsageInBytes.value
|
||||
: this.quotaUsageInBytes,
|
||||
pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('AuthUserEntityData(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('email: $email, ')
|
||||
..write('isAdmin: $isAdmin, ')
|
||||
..write('hasProfileImage: $hasProfileImage, ')
|
||||
..write('profileChangedAt: $profileChangedAt, ')
|
||||
..write('avatarColor: $avatarColor, ')
|
||||
..write('quotaSizeInBytes: $quotaSizeInBytes, ')
|
||||
..write('quotaUsageInBytes: $quotaUsageInBytes, ')
|
||||
..write('pinCode: $pinCode')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
isAdmin,
|
||||
hasProfileImage,
|
||||
profileChangedAt,
|
||||
avatarColor,
|
||||
quotaSizeInBytes,
|
||||
quotaUsageInBytes,
|
||||
pinCode,
|
||||
);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.AuthUserEntityData &&
|
||||
other.id == this.id &&
|
||||
other.name == this.name &&
|
||||
other.email == this.email &&
|
||||
other.isAdmin == this.isAdmin &&
|
||||
other.hasProfileImage == this.hasProfileImage &&
|
||||
other.profileChangedAt == this.profileChangedAt &&
|
||||
other.avatarColor == this.avatarColor &&
|
||||
other.quotaSizeInBytes == this.quotaSizeInBytes &&
|
||||
other.quotaUsageInBytes == this.quotaUsageInBytes &&
|
||||
other.pinCode == this.pinCode);
|
||||
}
|
||||
|
||||
class AuthUserEntityCompanion
|
||||
extends i0.UpdateCompanion<i1.AuthUserEntityData> {
|
||||
final i0.Value<String> id;
|
||||
final i0.Value<String> name;
|
||||
final i0.Value<String> email;
|
||||
final i0.Value<bool> isAdmin;
|
||||
final i0.Value<bool> hasProfileImage;
|
||||
final i0.Value<DateTime> profileChangedAt;
|
||||
final i0.Value<i2.AvatarColor> avatarColor;
|
||||
final i0.Value<int> quotaSizeInBytes;
|
||||
final i0.Value<int> quotaUsageInBytes;
|
||||
final i0.Value<String?> pinCode;
|
||||
const AuthUserEntityCompanion({
|
||||
this.id = const i0.Value.absent(),
|
||||
this.name = const i0.Value.absent(),
|
||||
this.email = const i0.Value.absent(),
|
||||
this.isAdmin = const i0.Value.absent(),
|
||||
this.hasProfileImage = const i0.Value.absent(),
|
||||
this.profileChangedAt = const i0.Value.absent(),
|
||||
this.avatarColor = const i0.Value.absent(),
|
||||
this.quotaSizeInBytes = const i0.Value.absent(),
|
||||
this.quotaUsageInBytes = const i0.Value.absent(),
|
||||
this.pinCode = const i0.Value.absent(),
|
||||
});
|
||||
AuthUserEntityCompanion.insert({
|
||||
required String id,
|
||||
required String name,
|
||||
required String email,
|
||||
this.isAdmin = const i0.Value.absent(),
|
||||
this.hasProfileImage = const i0.Value.absent(),
|
||||
this.profileChangedAt = const i0.Value.absent(),
|
||||
required i2.AvatarColor avatarColor,
|
||||
this.quotaSizeInBytes = const i0.Value.absent(),
|
||||
this.quotaUsageInBytes = const i0.Value.absent(),
|
||||
this.pinCode = const i0.Value.absent(),
|
||||
}) : id = i0.Value(id),
|
||||
name = i0.Value(name),
|
||||
email = i0.Value(email),
|
||||
avatarColor = i0.Value(avatarColor);
|
||||
static i0.Insertable<i1.AuthUserEntityData> custom({
|
||||
i0.Expression<String>? id,
|
||||
i0.Expression<String>? name,
|
||||
i0.Expression<String>? email,
|
||||
i0.Expression<bool>? isAdmin,
|
||||
i0.Expression<bool>? hasProfileImage,
|
||||
i0.Expression<DateTime>? profileChangedAt,
|
||||
i0.Expression<int>? avatarColor,
|
||||
i0.Expression<int>? quotaSizeInBytes,
|
||||
i0.Expression<int>? quotaUsageInBytes,
|
||||
i0.Expression<String>? pinCode,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (name != null) 'name': name,
|
||||
if (email != null) 'email': email,
|
||||
if (isAdmin != null) 'is_admin': isAdmin,
|
||||
if (hasProfileImage != null) 'has_profile_image': hasProfileImage,
|
||||
if (profileChangedAt != null) 'profile_changed_at': profileChangedAt,
|
||||
if (avatarColor != null) 'avatar_color': avatarColor,
|
||||
if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes,
|
||||
if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes,
|
||||
if (pinCode != null) 'pin_code': pinCode,
|
||||
});
|
||||
}
|
||||
|
||||
i1.AuthUserEntityCompanion copyWith({
|
||||
i0.Value<String>? id,
|
||||
i0.Value<String>? name,
|
||||
i0.Value<String>? email,
|
||||
i0.Value<bool>? isAdmin,
|
||||
i0.Value<bool>? hasProfileImage,
|
||||
i0.Value<DateTime>? profileChangedAt,
|
||||
i0.Value<i2.AvatarColor>? avatarColor,
|
||||
i0.Value<int>? quotaSizeInBytes,
|
||||
i0.Value<int>? quotaUsageInBytes,
|
||||
i0.Value<String?>? pinCode,
|
||||
}) {
|
||||
return i1.AuthUserEntityCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
email: email ?? this.email,
|
||||
isAdmin: isAdmin ?? this.isAdmin,
|
||||
hasProfileImage: hasProfileImage ?? this.hasProfileImage,
|
||||
profileChangedAt: profileChangedAt ?? this.profileChangedAt,
|
||||
avatarColor: avatarColor ?? this.avatarColor,
|
||||
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
|
||||
pinCode: pinCode ?? this.pinCode,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = i0.Variable<String>(id.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = i0.Variable<String>(name.value);
|
||||
}
|
||||
if (email.present) {
|
||||
map['email'] = i0.Variable<String>(email.value);
|
||||
}
|
||||
if (isAdmin.present) {
|
||||
map['is_admin'] = i0.Variable<bool>(isAdmin.value);
|
||||
}
|
||||
if (hasProfileImage.present) {
|
||||
map['has_profile_image'] = i0.Variable<bool>(hasProfileImage.value);
|
||||
}
|
||||
if (profileChangedAt.present) {
|
||||
map['profile_changed_at'] = i0.Variable<DateTime>(profileChangedAt.value);
|
||||
}
|
||||
if (avatarColor.present) {
|
||||
map['avatar_color'] = i0.Variable<int>(
|
||||
i1.$AuthUserEntityTable.$converteravatarColor.toSql(avatarColor.value),
|
||||
);
|
||||
}
|
||||
if (quotaSizeInBytes.present) {
|
||||
map['quota_size_in_bytes'] = i0.Variable<int>(quotaSizeInBytes.value);
|
||||
}
|
||||
if (quotaUsageInBytes.present) {
|
||||
map['quota_usage_in_bytes'] = i0.Variable<int>(quotaUsageInBytes.value);
|
||||
}
|
||||
if (pinCode.present) {
|
||||
map['pin_code'] = i0.Variable<String>(pinCode.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('AuthUserEntityCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('email: $email, ')
|
||||
..write('isAdmin: $isAdmin, ')
|
||||
..write('hasProfileImage: $hasProfileImage, ')
|
||||
..write('profileChangedAt: $profileChangedAt, ')
|
||||
..write('avatarColor: $avatarColor, ')
|
||||
..write('quotaSizeInBytes: $quotaSizeInBytes, ')
|
||||
..write('quotaUsageInBytes: $quotaUsageInBytes, ')
|
||||
..write('pinCode: $pinCode')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:drift/drift.dart' hide Index;
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
import 'package:immich_mobile/utils/hash.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
@@ -44,7 +43,7 @@ class User {
|
||||
|
||||
static User fromDto(UserDto dto) => User(
|
||||
id: dto.id,
|
||||
updatedAt: dto.updatedAt,
|
||||
updatedAt: dto.updatedAt ?? DateTime(2025),
|
||||
email: dto.email,
|
||||
name: dto.name,
|
||||
isAdmin: dto.isAdmin,
|
||||
@@ -81,13 +80,12 @@ class UserEntity extends Table with DriftDefaultsMixin {
|
||||
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
BoolColumn get isAdmin => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get email => text()();
|
||||
|
||||
// Profile image
|
||||
BoolColumn get hasProfileImage => boolean().withDefault(const Constant(false))();
|
||||
DateTimeColumn get profileChangedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
IntColumn get avatarColor => intEnum<AvatarColor>().withDefault(const Constant(0))();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
|
||||
@@ -3,28 +3,27 @@
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i1;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as i2;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
||||
import 'package:immich_mobile/domain/models/user.model.dart' as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as i3;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||
|
||||
typedef $$UserEntityTableCreateCompanionBuilder =
|
||||
i1.UserEntityCompanion Function({
|
||||
required String id,
|
||||
required String name,
|
||||
i0.Value<bool> isAdmin,
|
||||
required String email,
|
||||
i0.Value<bool> hasProfileImage,
|
||||
i0.Value<DateTime> profileChangedAt,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
i0.Value<i2.AvatarColor> avatarColor,
|
||||
});
|
||||
typedef $$UserEntityTableUpdateCompanionBuilder =
|
||||
i1.UserEntityCompanion Function({
|
||||
i0.Value<String> id,
|
||||
i0.Value<String> name,
|
||||
i0.Value<bool> isAdmin,
|
||||
i0.Value<String> email,
|
||||
i0.Value<bool> hasProfileImage,
|
||||
i0.Value<DateTime> profileChangedAt,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
i0.Value<i2.AvatarColor> avatarColor,
|
||||
});
|
||||
|
||||
class $$UserEntityTableFilterComposer
|
||||
@@ -46,11 +45,6 @@ class $$UserEntityTableFilterComposer
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<bool> get isAdmin => $composableBuilder(
|
||||
column: $table.isAdmin,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<String> get email => $composableBuilder(
|
||||
column: $table.email,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
@@ -66,9 +60,10 @@ class $$UserEntityTableFilterComposer
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
i0.ColumnWithTypeConverterFilters<i2.AvatarColor, i2.AvatarColor, int>
|
||||
get avatarColor => $composableBuilder(
|
||||
column: $table.avatarColor,
|
||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,11 +86,6 @@ class $$UserEntityTableOrderingComposer
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<bool> get isAdmin => $composableBuilder(
|
||||
column: $table.isAdmin,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<String> get email => $composableBuilder(
|
||||
column: $table.email,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
@@ -111,8 +101,8 @@ class $$UserEntityTableOrderingComposer
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt,
|
||||
i0.ColumnOrderings<int> get avatarColor => $composableBuilder(
|
||||
column: $table.avatarColor,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
@@ -132,9 +122,6 @@ class $$UserEntityTableAnnotationComposer
|
||||
i0.GeneratedColumn<String> get name =>
|
||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<bool> get isAdmin =>
|
||||
$composableBuilder(column: $table.isAdmin, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get email =>
|
||||
$composableBuilder(column: $table.email, builder: (column) => column);
|
||||
|
||||
@@ -148,8 +135,11 @@ class $$UserEntityTableAnnotationComposer
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||
i0.GeneratedColumnWithTypeConverter<i2.AvatarColor, int> get avatarColor =>
|
||||
$composableBuilder(
|
||||
column: $table.avatarColor,
|
||||
builder: (column) => column,
|
||||
);
|
||||
}
|
||||
|
||||
class $$UserEntityTableTableManager
|
||||
@@ -191,37 +181,33 @@ class $$UserEntityTableTableManager
|
||||
({
|
||||
i0.Value<String> id = const i0.Value.absent(),
|
||||
i0.Value<String> name = const i0.Value.absent(),
|
||||
i0.Value<bool> isAdmin = const i0.Value.absent(),
|
||||
i0.Value<String> email = const i0.Value.absent(),
|
||||
i0.Value<bool> hasProfileImage = const i0.Value.absent(),
|
||||
i0.Value<DateTime> profileChangedAt = const i0.Value.absent(),
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
i0.Value<i2.AvatarColor> avatarColor = const i0.Value.absent(),
|
||||
}) => i1.UserEntityCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
isAdmin: isAdmin,
|
||||
email: email,
|
||||
hasProfileImage: hasProfileImage,
|
||||
profileChangedAt: profileChangedAt,
|
||||
updatedAt: updatedAt,
|
||||
avatarColor: avatarColor,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
required String id,
|
||||
required String name,
|
||||
i0.Value<bool> isAdmin = const i0.Value.absent(),
|
||||
required String email,
|
||||
i0.Value<bool> hasProfileImage = const i0.Value.absent(),
|
||||
i0.Value<DateTime> profileChangedAt = const i0.Value.absent(),
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
i0.Value<i2.AvatarColor> avatarColor = const i0.Value.absent(),
|
||||
}) => i1.UserEntityCompanion.insert(
|
||||
id: id,
|
||||
name: name,
|
||||
isAdmin: isAdmin,
|
||||
email: email,
|
||||
hasProfileImage: hasProfileImage,
|
||||
profileChangedAt: profileChangedAt,
|
||||
updatedAt: updatedAt,
|
||||
avatarColor: avatarColor,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||
@@ -253,7 +239,7 @@ typedef $$UserEntityTableProcessedTableManager =
|
||||
i0.PrefetchHooks Function()
|
||||
>;
|
||||
|
||||
class $UserEntityTable extends i2.UserEntity
|
||||
class $UserEntityTable extends i3.UserEntity
|
||||
with i0.TableInfo<$UserEntityTable, i1.UserEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
@@ -279,21 +265,6 @@ class $UserEntityTable extends i2.UserEntity
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const i0.VerificationMeta _isAdminMeta = const i0.VerificationMeta(
|
||||
'isAdmin',
|
||||
);
|
||||
@override
|
||||
late final i0.GeneratedColumn<bool> isAdmin = i0.GeneratedColumn<bool>(
|
||||
'is_admin',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("is_admin" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const i3.Constant(false),
|
||||
);
|
||||
static const i0.VerificationMeta _emailMeta = const i0.VerificationMeta(
|
||||
'email',
|
||||
);
|
||||
@@ -318,7 +289,7 @@ class $UserEntityTable extends i2.UserEntity
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("has_profile_image" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const i3.Constant(false),
|
||||
defaultValue: const i4.Constant(false),
|
||||
);
|
||||
static const i0.VerificationMeta _profileChangedAtMeta =
|
||||
const i0.VerificationMeta('profileChangedAt');
|
||||
@@ -330,30 +301,26 @@ class $UserEntityTable extends i2.UserEntity
|
||||
false,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: i3.currentDateAndTime,
|
||||
defaultValue: i4.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,
|
||||
);
|
||||
late final i0.GeneratedColumnWithTypeConverter<i2.AvatarColor, int>
|
||||
avatarColor = i0.GeneratedColumn<int>(
|
||||
'avatar_color',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const i4.Constant(0),
|
||||
).withConverter<i2.AvatarColor>(i1.$UserEntityTable.$converteravatarColor);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [
|
||||
id,
|
||||
name,
|
||||
isAdmin,
|
||||
email,
|
||||
hasProfileImage,
|
||||
profileChangedAt,
|
||||
updatedAt,
|
||||
avatarColor,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@@ -380,12 +347,6 @@ class $UserEntityTable extends i2.UserEntity
|
||||
} else if (isInserting) {
|
||||
context.missing(_nameMeta);
|
||||
}
|
||||
if (data.containsKey('is_admin')) {
|
||||
context.handle(
|
||||
_isAdminMeta,
|
||||
isAdmin.isAcceptableOrUnknown(data['is_admin']!, _isAdminMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('email')) {
|
||||
context.handle(
|
||||
_emailMeta,
|
||||
@@ -412,12 +373,6 @@ class $UserEntityTable extends i2.UserEntity
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('updated_at')) {
|
||||
context.handle(
|
||||
_updatedAtMeta,
|
||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -435,10 +390,6 @@ class $UserEntityTable extends i2.UserEntity
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}name'],
|
||||
)!,
|
||||
isAdmin: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool,
|
||||
data['${effectivePrefix}is_admin'],
|
||||
)!,
|
||||
email: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}email'],
|
||||
@@ -451,10 +402,12 @@ class $UserEntityTable extends i2.UserEntity
|
||||
i0.DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}profile_changed_at'],
|
||||
)!,
|
||||
updatedAt: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}updated_at'],
|
||||
)!,
|
||||
avatarColor: i1.$UserEntityTable.$converteravatarColor.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int,
|
||||
data['${effectivePrefix}avatar_color'],
|
||||
)!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -463,6 +416,8 @@ class $UserEntityTable extends i2.UserEntity
|
||||
return $UserEntityTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static i0.JsonTypeConverter2<i2.AvatarColor, int, int> $converteravatarColor =
|
||||
const i0.EnumIndexConverter<i2.AvatarColor>(i2.AvatarColor.values);
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
@override
|
||||
@@ -473,30 +428,31 @@ class UserEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.UserEntityData> {
|
||||
final String id;
|
||||
final String name;
|
||||
final bool isAdmin;
|
||||
final String email;
|
||||
final bool hasProfileImage;
|
||||
final DateTime profileChangedAt;
|
||||
final DateTime updatedAt;
|
||||
final i2.AvatarColor avatarColor;
|
||||
const UserEntityData({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.isAdmin,
|
||||
required this.email,
|
||||
required this.hasProfileImage,
|
||||
required this.profileChangedAt,
|
||||
required this.updatedAt,
|
||||
required this.avatarColor,
|
||||
});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['id'] = i0.Variable<String>(id);
|
||||
map['name'] = i0.Variable<String>(name);
|
||||
map['is_admin'] = i0.Variable<bool>(isAdmin);
|
||||
map['email'] = i0.Variable<String>(email);
|
||||
map['has_profile_image'] = i0.Variable<bool>(hasProfileImage);
|
||||
map['profile_changed_at'] = i0.Variable<DateTime>(profileChangedAt);
|
||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||
{
|
||||
map['avatar_color'] = i0.Variable<int>(
|
||||
i1.$UserEntityTable.$converteravatarColor.toSql(avatarColor),
|
||||
);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -508,11 +464,12 @@ class UserEntityData extends i0.DataClass
|
||||
return UserEntityData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
isAdmin: serializer.fromJson<bool>(json['isAdmin']),
|
||||
email: serializer.fromJson<String>(json['email']),
|
||||
hasProfileImage: serializer.fromJson<bool>(json['hasProfileImage']),
|
||||
profileChangedAt: serializer.fromJson<DateTime>(json['profileChangedAt']),
|
||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||
avatarColor: i1.$UserEntityTable.$converteravatarColor.fromJson(
|
||||
serializer.fromJson<int>(json['avatarColor']),
|
||||
),
|
||||
);
|
||||
}
|
||||
@override
|
||||
@@ -521,36 +478,34 @@ class UserEntityData extends i0.DataClass
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'isAdmin': serializer.toJson<bool>(isAdmin),
|
||||
'email': serializer.toJson<String>(email),
|
||||
'hasProfileImage': serializer.toJson<bool>(hasProfileImage),
|
||||
'profileChangedAt': serializer.toJson<DateTime>(profileChangedAt),
|
||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||
'avatarColor': serializer.toJson<int>(
|
||||
i1.$UserEntityTable.$converteravatarColor.toJson(avatarColor),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
i1.UserEntityData copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
bool? isAdmin,
|
||||
String? email,
|
||||
bool? hasProfileImage,
|
||||
DateTime? profileChangedAt,
|
||||
DateTime? updatedAt,
|
||||
i2.AvatarColor? avatarColor,
|
||||
}) => i1.UserEntityData(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
isAdmin: isAdmin ?? this.isAdmin,
|
||||
email: email ?? this.email,
|
||||
hasProfileImage: hasProfileImage ?? this.hasProfileImage,
|
||||
profileChangedAt: profileChangedAt ?? this.profileChangedAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
avatarColor: avatarColor ?? this.avatarColor,
|
||||
);
|
||||
UserEntityData copyWithCompanion(i1.UserEntityCompanion data) {
|
||||
return UserEntityData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
name: data.name.present ? data.name.value : this.name,
|
||||
isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin,
|
||||
email: data.email.present ? data.email.value : this.email,
|
||||
hasProfileImage: data.hasProfileImage.present
|
||||
? data.hasProfileImage.value
|
||||
@@ -558,7 +513,9 @@ class UserEntityData extends i0.DataClass
|
||||
profileChangedAt: data.profileChangedAt.present
|
||||
? data.profileChangedAt.value
|
||||
: this.profileChangedAt,
|
||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||
avatarColor: data.avatarColor.present
|
||||
? data.avatarColor.value
|
||||
: this.avatarColor,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -567,11 +524,10 @@ class UserEntityData extends i0.DataClass
|
||||
return (StringBuffer('UserEntityData(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('isAdmin: $isAdmin, ')
|
||||
..write('email: $email, ')
|
||||
..write('hasProfileImage: $hasProfileImage, ')
|
||||
..write('profileChangedAt: $profileChangedAt, ')
|
||||
..write('updatedAt: $updatedAt')
|
||||
..write('avatarColor: $avatarColor')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
@@ -580,11 +536,10 @@ class UserEntityData extends i0.DataClass
|
||||
int get hashCode => Object.hash(
|
||||
id,
|
||||
name,
|
||||
isAdmin,
|
||||
email,
|
||||
hasProfileImage,
|
||||
profileChangedAt,
|
||||
updatedAt,
|
||||
avatarColor,
|
||||
);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
@@ -592,78 +547,70 @@ class UserEntityData extends i0.DataClass
|
||||
(other is i1.UserEntityData &&
|
||||
other.id == this.id &&
|
||||
other.name == this.name &&
|
||||
other.isAdmin == this.isAdmin &&
|
||||
other.email == this.email &&
|
||||
other.hasProfileImage == this.hasProfileImage &&
|
||||
other.profileChangedAt == this.profileChangedAt &&
|
||||
other.updatedAt == this.updatedAt);
|
||||
other.avatarColor == this.avatarColor);
|
||||
}
|
||||
|
||||
class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
final i0.Value<String> id;
|
||||
final i0.Value<String> name;
|
||||
final i0.Value<bool> isAdmin;
|
||||
final i0.Value<String> email;
|
||||
final i0.Value<bool> hasProfileImage;
|
||||
final i0.Value<DateTime> profileChangedAt;
|
||||
final i0.Value<DateTime> updatedAt;
|
||||
final i0.Value<i2.AvatarColor> avatarColor;
|
||||
const UserEntityCompanion({
|
||||
this.id = const i0.Value.absent(),
|
||||
this.name = const i0.Value.absent(),
|
||||
this.isAdmin = const i0.Value.absent(),
|
||||
this.email = const i0.Value.absent(),
|
||||
this.hasProfileImage = const i0.Value.absent(),
|
||||
this.profileChangedAt = const i0.Value.absent(),
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
this.avatarColor = const i0.Value.absent(),
|
||||
});
|
||||
UserEntityCompanion.insert({
|
||||
required String id,
|
||||
required String name,
|
||||
this.isAdmin = const i0.Value.absent(),
|
||||
required String email,
|
||||
this.hasProfileImage = const i0.Value.absent(),
|
||||
this.profileChangedAt = const i0.Value.absent(),
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
this.avatarColor = const i0.Value.absent(),
|
||||
}) : id = i0.Value(id),
|
||||
name = i0.Value(name),
|
||||
email = i0.Value(email);
|
||||
static i0.Insertable<i1.UserEntityData> custom({
|
||||
i0.Expression<String>? id,
|
||||
i0.Expression<String>? name,
|
||||
i0.Expression<bool>? isAdmin,
|
||||
i0.Expression<String>? email,
|
||||
i0.Expression<bool>? hasProfileImage,
|
||||
i0.Expression<DateTime>? profileChangedAt,
|
||||
i0.Expression<DateTime>? updatedAt,
|
||||
i0.Expression<int>? avatarColor,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (name != null) 'name': name,
|
||||
if (isAdmin != null) 'is_admin': isAdmin,
|
||||
if (email != null) 'email': email,
|
||||
if (hasProfileImage != null) 'has_profile_image': hasProfileImage,
|
||||
if (profileChangedAt != null) 'profile_changed_at': profileChangedAt,
|
||||
if (updatedAt != null) 'updated_at': updatedAt,
|
||||
if (avatarColor != null) 'avatar_color': avatarColor,
|
||||
});
|
||||
}
|
||||
|
||||
i1.UserEntityCompanion copyWith({
|
||||
i0.Value<String>? id,
|
||||
i0.Value<String>? name,
|
||||
i0.Value<bool>? isAdmin,
|
||||
i0.Value<String>? email,
|
||||
i0.Value<bool>? hasProfileImage,
|
||||
i0.Value<DateTime>? profileChangedAt,
|
||||
i0.Value<DateTime>? updatedAt,
|
||||
i0.Value<i2.AvatarColor>? avatarColor,
|
||||
}) {
|
||||
return i1.UserEntityCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
isAdmin: isAdmin ?? this.isAdmin,
|
||||
email: email ?? this.email,
|
||||
hasProfileImage: hasProfileImage ?? this.hasProfileImage,
|
||||
profileChangedAt: profileChangedAt ?? this.profileChangedAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
avatarColor: avatarColor ?? this.avatarColor,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -676,9 +623,6 @@ class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
if (name.present) {
|
||||
map['name'] = i0.Variable<String>(name.value);
|
||||
}
|
||||
if (isAdmin.present) {
|
||||
map['is_admin'] = i0.Variable<bool>(isAdmin.value);
|
||||
}
|
||||
if (email.present) {
|
||||
map['email'] = i0.Variable<String>(email.value);
|
||||
}
|
||||
@@ -688,8 +632,10 @@ class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
if (profileChangedAt.present) {
|
||||
map['profile_changed_at'] = i0.Variable<DateTime>(profileChangedAt.value);
|
||||
}
|
||||
if (updatedAt.present) {
|
||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||
if (avatarColor.present) {
|
||||
map['avatar_color'] = i0.Variable<int>(
|
||||
i1.$UserEntityTable.$converteravatarColor.toSql(avatarColor.value),
|
||||
);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@@ -699,11 +645,10 @@ class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
return (StringBuffer('UserEntityCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('isAdmin: $isAdmin, ')
|
||||
..write('email: $email, ')
|
||||
..write('hasProfileImage: $hasProfileImage, ')
|
||||
..write('profileChangedAt: $profileChangedAt, ')
|
||||
..write('updatedAt: $updatedAt')
|
||||
..write('avatarColor: $avatarColor')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:drift/drift.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
@@ -138,22 +137,4 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
||||
|
||||
return query.map((localAsset) => localAsset.toDto()).get();
|
||||
}
|
||||
|
||||
FutureOr<List<LocalAlbum>> getSourceAlbums(String localAssetId) {
|
||||
final query = _db.localAlbumEntity.select()
|
||||
..where(
|
||||
(lae) =>
|
||||
existsQuery(
|
||||
_db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.albumId])
|
||||
..where(
|
||||
_db.localAlbumAssetEntity.albumId.equalsExp(lae.id) &
|
||||
_db.localAlbumAssetEntity.assetId.equals(localAssetId),
|
||||
),
|
||||
) &
|
||||
lae.backupSelection.equalsValue(BackupSelection.selected),
|
||||
)
|
||||
..orderBy([(lae) => OrderingTerm.asc(lae.name)]);
|
||||
return query.map((localAlbum) => localAlbum.toDto()).get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:drift_flutter/drift_flutter.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
|
||||
@@ -43,6 +44,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [
|
||||
AuthUserEntity,
|
||||
UserEntity,
|
||||
UserMetadataEntity,
|
||||
PartnerEntity,
|
||||
@@ -68,7 +70,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
||||
: super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true)));
|
||||
|
||||
@override
|
||||
int get schemaVersion => 9;
|
||||
int get schemaVersion => 10;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -126,6 +128,11 @@ class Drift extends $Drift implements IDatabaseRepository {
|
||||
from8To9: (m, v9) async {
|
||||
await m.addColumn(v9.localAlbumEntity, v9.localAlbumEntity.linkedRemoteAlbumId);
|
||||
},
|
||||
from9To10: (m, v10) async {
|
||||
await m.createTable(v10.authUserEntity);
|
||||
await m.addColumn(v10.userEntity, v10.userEntity.avatarColor);
|
||||
await m.alterTable(TableMigration(v10.userEntity));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -141,7 +148,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
||||
await customStatement('PRAGMA foreign_keys = ON');
|
||||
await customStatement('PRAGMA synchronous = NORMAL');
|
||||
await customStatement('PRAGMA journal_mode = WAL');
|
||||
await customStatement('PRAGMA busy_timeout = 500');
|
||||
await customStatement('PRAGMA busy_timeout = 30000');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,29 +15,31 @@ import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.d
|
||||
as i6;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||
as i7;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'
|
||||
as i8;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
as i9;
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
as i10;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
||||
as i11;
|
||||
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 i12;
|
||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
||||
as i13;
|
||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
||||
as i14;
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
||||
as i15;
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
|
||||
as i16;
|
||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
||||
as i17;
|
||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
||||
as i18;
|
||||
import 'package:drift/internal/modular.dart' as i19;
|
||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||
as i19;
|
||||
import 'package:drift/internal/modular.dart' as i20;
|
||||
|
||||
abstract class $Drift extends i0.GeneratedDatabase {
|
||||
$Drift(i0.QueryExecutor e) : super(e);
|
||||
@@ -54,27 +56,30 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
.$LocalAlbumEntityTable(this);
|
||||
late final i7.$LocalAlbumAssetEntityTable localAlbumAssetEntity = i7
|
||||
.$LocalAlbumAssetEntityTable(this);
|
||||
late final i8.$UserMetadataEntityTable userMetadataEntity = i8
|
||||
.$UserMetadataEntityTable(this);
|
||||
late final i9.$PartnerEntityTable partnerEntity = i9.$PartnerEntityTable(
|
||||
late final i8.$AuthUserEntityTable authUserEntity = i8.$AuthUserEntityTable(
|
||||
this,
|
||||
);
|
||||
late final i10.$RemoteExifEntityTable remoteExifEntity = i10
|
||||
.$RemoteExifEntityTable(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.$PersonEntityTable personEntity = i15.$PersonEntityTable(this);
|
||||
late final i16.$AssetFaceEntityTable assetFaceEntity = i16
|
||||
.$AssetFaceEntityTable(this);
|
||||
late final i17.$StoreEntityTable storeEntity = i17.$StoreEntityTable(this);
|
||||
i18.MergedAssetDrift get mergedAssetDrift => i19.ReadDatabaseContainer(
|
||||
late final i9.$UserMetadataEntityTable userMetadataEntity = i9
|
||||
.$UserMetadataEntityTable(this);
|
||||
late final i10.$PartnerEntityTable partnerEntity = i10.$PartnerEntityTable(
|
||||
this,
|
||||
).accessor<i18.MergedAssetDrift>(i18.MergedAssetDrift.new);
|
||||
);
|
||||
late final i11.$RemoteExifEntityTable remoteExifEntity = i11
|
||||
.$RemoteExifEntityTable(this);
|
||||
late final i12.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity = i12
|
||||
.$RemoteAlbumAssetEntityTable(this);
|
||||
late final i13.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i13
|
||||
.$RemoteAlbumUserEntityTable(this);
|
||||
late final i14.$MemoryEntityTable memoryEntity = i14.$MemoryEntityTable(this);
|
||||
late final i15.$MemoryAssetEntityTable memoryAssetEntity = i15
|
||||
.$MemoryAssetEntityTable(this);
|
||||
late final i16.$PersonEntityTable personEntity = i16.$PersonEntityTable(this);
|
||||
late final i17.$AssetFaceEntityTable assetFaceEntity = i17
|
||||
.$AssetFaceEntityTable(this);
|
||||
late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this);
|
||||
i19.MergedAssetDrift get mergedAssetDrift => i20.ReadDatabaseContainer(
|
||||
this,
|
||||
).accessor<i19.MergedAssetDrift>(i19.MergedAssetDrift.new);
|
||||
@override
|
||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||
@@ -92,6 +97,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i2.uQRemoteAssetsOwnerChecksum,
|
||||
i2.uQRemoteAssetsOwnerLibraryChecksum,
|
||||
i2.idxRemoteAssetChecksum,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteExifEntity,
|
||||
@@ -102,7 +108,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
personEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
i10.idxLatLng,
|
||||
i11.idxLatLng,
|
||||
];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules
|
||||
@@ -305,27 +311,29 @@ class $DriftManager {
|
||||
i6.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
||||
i7.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i7
|
||||
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
|
||||
i8.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
|
||||
i8.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||
i9.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||
i9.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||
i10.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
||||
i10.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
||||
i11.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
|
||||
i11.$$RemoteAlbumAssetEntityTableTableManager(
|
||||
i8.$$AuthUserEntityTableTableManager get authUserEntity =>
|
||||
i8.$$AuthUserEntityTableTableManager(_db, _db.authUserEntity);
|
||||
i9.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
|
||||
i9.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||
i10.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||
i10.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||
i11.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
||||
i11.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
||||
i12.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
|
||||
i12.$$RemoteAlbumAssetEntityTableTableManager(
|
||||
_db,
|
||||
_db.remoteAlbumAssetEntity,
|
||||
);
|
||||
i12.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i12
|
||||
i13.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i13
|
||||
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
|
||||
i13.$$MemoryEntityTableTableManager get memoryEntity =>
|
||||
i13.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
||||
i14.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
||||
i14.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
||||
i15.$$PersonEntityTableTableManager get personEntity =>
|
||||
i15.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
||||
i16.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
||||
i16.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
||||
i17.$$StoreEntityTableTableManager get storeEntity =>
|
||||
i17.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
||||
i14.$$MemoryEntityTableTableManager get memoryEntity =>
|
||||
i14.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
||||
i15.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
||||
i15.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
||||
i16.$$PersonEntityTableTableManager get personEntity =>
|
||||
i16.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
||||
i17.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
||||
i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
||||
i18.$$StoreEntityTableTableManager get storeEntity =>
|
||||
i18.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
||||
}
|
||||
|
||||
@@ -3820,6 +3820,456 @@ i1.GeneratedColumn<String> _column_90(String aliasedName) =>
|
||||
'REFERENCES remote_album_entity (id) ON DELETE SET NULL',
|
||||
),
|
||||
);
|
||||
|
||||
final class Schema10 extends i0.VersionedSchema {
|
||||
Schema10({required super.database}) : super(version: 10);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
userEntity,
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
idxLocalAssetChecksum,
|
||||
idxRemoteAssetOwnerChecksum,
|
||||
uQRemoteAssetsOwnerChecksum,
|
||||
uQRemoteAssetsOwnerLibraryChecksum,
|
||||
idxRemoteAssetChecksum,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity,
|
||||
personEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
idxLatLng,
|
||||
];
|
||||
late final Shape20 userEntity = Shape20(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_3,
|
||||
_column_84,
|
||||
_column_85,
|
||||
_column_91,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape17 remoteAssetEntity = Shape17(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_1,
|
||||
_column_8,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_0,
|
||||
_column_13,
|
||||
_column_14,
|
||||
_column_15,
|
||||
_column_16,
|
||||
_column_17,
|
||||
_column_18,
|
||||
_column_19,
|
||||
_column_20,
|
||||
_column_21,
|
||||
_column_86,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape3 stackEntity = Shape3(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'stack_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape2 localAssetEntity = Shape2(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_1,
|
||||
_column_8,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_0,
|
||||
_column_22,
|
||||
_column_14,
|
||||
_column_23,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape9 remoteAlbumEntity = Shape9(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_56,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_15,
|
||||
_column_57,
|
||||
_column_58,
|
||||
_column_59,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape19 localAlbumEntity = Shape19(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_5,
|
||||
_column_31,
|
||||
_column_32,
|
||||
_column_90,
|
||||
_column_33,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape7 localAlbumAssetEntity = Shape7(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||
columns: [_column_34, _column_35],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||
'idx_local_asset_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
||||
'idx_remote_asset_owner_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
||||
);
|
||||
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||
'UQ_remote_assets_owner_checksum',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||
);
|
||||
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||
'UQ_remote_assets_owner_library_checksum',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||
'idx_remote_asset_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||
);
|
||||
late final Shape21 authUserEntity = Shape21(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'auth_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_3,
|
||||
_column_2,
|
||||
_column_84,
|
||||
_column_85,
|
||||
_column_92,
|
||||
_column_93,
|
||||
_column_7,
|
||||
_column_94,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape4 userMetadataEntity = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_metadata_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||
columns: [_column_25, _column_26, _column_27],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape5 partnerEntity = Shape5(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'partner_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||
columns: [_column_28, _column_29, _column_30],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape8 remoteExifEntity = Shape8(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_exif_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||
columns: [
|
||||
_column_36,
|
||||
_column_37,
|
||||
_column_38,
|
||||
_column_39,
|
||||
_column_40,
|
||||
_column_41,
|
||||
_column_11,
|
||||
_column_10,
|
||||
_column_42,
|
||||
_column_43,
|
||||
_column_44,
|
||||
_column_45,
|
||||
_column_46,
|
||||
_column_47,
|
||||
_column_48,
|
||||
_column_49,
|
||||
_column_50,
|
||||
_column_51,
|
||||
_column_52,
|
||||
_column_53,
|
||||
_column_54,
|
||||
_column_55,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||
columns: [_column_36, _column_60],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||
columns: [_column_60, _column_25, _column_61],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape11 memoryEntity = Shape11(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'memory_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_18,
|
||||
_column_15,
|
||||
_column_8,
|
||||
_column_62,
|
||||
_column_63,
|
||||
_column_64,
|
||||
_column_65,
|
||||
_column_66,
|
||||
_column_67,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape12 memoryAssetEntity = Shape12(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'memory_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||
columns: [_column_36, _column_68],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape14 personEntity = Shape14(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'person_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_15,
|
||||
_column_1,
|
||||
_column_69,
|
||||
_column_71,
|
||||
_column_72,
|
||||
_column_73,
|
||||
_column_74,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape15 assetFaceEntity = Shape15(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_face_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_36,
|
||||
_column_76,
|
||||
_column_77,
|
||||
_column_78,
|
||||
_column_79,
|
||||
_column_80,
|
||||
_column_81,
|
||||
_column_82,
|
||||
_column_83,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape18 storeEntity = Shape18(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'store_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [_column_87, _column_88, _column_89],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxLatLng = i1.Index(
|
||||
'idx_lat_lng',
|
||||
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||
);
|
||||
}
|
||||
|
||||
class Shape20 extends i0.VersionedTable {
|
||||
Shape20({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get id =>
|
||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get name =>
|
||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get email =>
|
||||
columnsByName['email']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<bool> get hasProfileImage =>
|
||||
columnsByName['has_profile_image']! as i1.GeneratedColumn<bool>;
|
||||
i1.GeneratedColumn<DateTime> get profileChangedAt =>
|
||||
columnsByName['profile_changed_at']! as i1.GeneratedColumn<DateTime>;
|
||||
i1.GeneratedColumn<int> get avatarColor =>
|
||||
columnsByName['avatar_color']! as i1.GeneratedColumn<int>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<int> _column_91(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'avatar_color',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
defaultValue: const CustomExpression('0'),
|
||||
);
|
||||
|
||||
class Shape21 extends i0.VersionedTable {
|
||||
Shape21({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get id =>
|
||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get name =>
|
||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get email =>
|
||||
columnsByName['email']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<bool> get isAdmin =>
|
||||
columnsByName['is_admin']! as i1.GeneratedColumn<bool>;
|
||||
i1.GeneratedColumn<bool> get hasProfileImage =>
|
||||
columnsByName['has_profile_image']! as i1.GeneratedColumn<bool>;
|
||||
i1.GeneratedColumn<DateTime> get profileChangedAt =>
|
||||
columnsByName['profile_changed_at']! as i1.GeneratedColumn<DateTime>;
|
||||
i1.GeneratedColumn<int> get avatarColor =>
|
||||
columnsByName['avatar_color']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get quotaSizeInBytes =>
|
||||
columnsByName['quota_size_in_bytes']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get quotaUsageInBytes =>
|
||||
columnsByName['quota_usage_in_bytes']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get pinCode =>
|
||||
columnsByName['pin_code']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<int> _column_92(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'avatar_color',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
);
|
||||
i1.GeneratedColumn<int> _column_93(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'quota_size_in_bytes',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
defaultValue: const CustomExpression('0'),
|
||||
);
|
||||
i1.GeneratedColumn<String> _column_94(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'pin_code',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i1.DriftSqlType.string,
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
@@ -3829,6 +4279,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
@@ -3872,6 +4323,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from8To9(migrator, schema);
|
||||
return 9;
|
||||
case 9:
|
||||
final schema = Schema10(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from9To10(migrator, schema);
|
||||
return 10;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
@@ -3887,6 +4343,7 @@ i1.OnUpgrade stepByStep({
|
||||
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
@@ -3897,5 +4354,6 @@ i1.OnUpgrade stepByStep({
|
||||
from6To7: from6To7,
|
||||
from7To8: from7To8,
|
||||
from8To9: from8To9,
|
||||
from9To10: from9To10,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
@@ -26,6 +28,12 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<LocalAsset?> get(String id) => _assetSelectable(id).getSingleOrNull();
|
||||
|
||||
Future<List<LocalAsset?>> getByChecksum(String checksum) {
|
||||
final query = _db.localAssetEntity.select()..where((lae) => lae.checksum.equals(checksum));
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
Stream<LocalAsset?> watch(String id) => _assetSelectable(id).watchSingleOrNull();
|
||||
|
||||
Future<void> updateHashes(Iterable<LocalAsset> hashes) {
|
||||
@@ -69,4 +77,23 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
Future<int> getHashedCount() {
|
||||
return _db.managers.localAssetEntity.filter((e) => e.checksum.isNull().not()).count();
|
||||
}
|
||||
|
||||
Future<List<LocalAlbum>> getSourceAlbums(String localAssetId, {BackupSelection? backupSelection}) {
|
||||
final query = _db.localAlbumEntity.select()
|
||||
..where(
|
||||
(lae) => existsQuery(
|
||||
_db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.albumId])
|
||||
..where(
|
||||
_db.localAlbumAssetEntity.albumId.equalsExp(lae.id) &
|
||||
_db.localAlbumAssetEntity.assetId.equals(localAssetId),
|
||||
),
|
||||
),
|
||||
)
|
||||
..orderBy([(lae) => OrderingTerm.asc(lae.name)]);
|
||||
if (backupSelection != null) {
|
||||
query.where((lae) => lae.backupSelection.equalsValue(backupSelection));
|
||||
}
|
||||
return query.map((localAlbum) => localAlbum.toDto()).get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,14 +202,13 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isAdmin: user.isAdmin,
|
||||
updatedAt: user.updatedAt,
|
||||
memoryEnabled: true,
|
||||
inTimeline: false,
|
||||
isPartnerSharedBy: false,
|
||||
isPartnerSharedWith: false,
|
||||
profileChangedAt: user.profileChangedAt,
|
||||
hasProfileImage: user.hasProfileImage,
|
||||
avatarColor: user.avatarColor,
|
||||
),
|
||||
)
|
||||
.get();
|
||||
|
||||
@@ -55,6 +55,12 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
return _assetSelectable(id).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<RemoteAsset?> getByChecksum(String checksum) {
|
||||
final query = _db.remoteAssetEntity.select()..where((row) => row.checksum.equals(checksum));
|
||||
|
||||
return query.map((row) => row.toDto()).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<List<RemoteAsset>> getStackChildren(RemoteAsset asset) {
|
||||
if (asset.stackId == null) {
|
||||
return Future.value([]);
|
||||
|
||||
@@ -173,7 +173,7 @@ class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepo
|
||||
const (bool) => entity.intValue == 1,
|
||||
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||
const (UserDto) =>
|
||||
entity.stringValue == null ? null : await DriftUserRepository(_db).get(entity.stringValue!),
|
||||
entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!),
|
||||
_ => null,
|
||||
}
|
||||
as T?;
|
||||
@@ -184,7 +184,7 @@ class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepo
|
||||
const (String) => (null, value as String),
|
||||
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||
const (UserDto) => (null, (await DriftUserRepository(_db).upsert(value as UserDto)).id),
|
||||
const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id),
|
||||
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
||||
};
|
||||
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
||||
|
||||
@@ -18,7 +18,8 @@ class SyncApiRepository {
|
||||
}
|
||||
|
||||
Future<void> streamChanges(
|
||||
Function(List<SyncEvent>, Function() abort) onData, {
|
||||
Future<void> Function(List<SyncEvent>, Function() abort, Function() reset) onData, {
|
||||
Function()? onReset,
|
||||
int batchSize = kSyncEventBatchSize,
|
||||
http.Client? httpClient,
|
||||
}) async {
|
||||
@@ -37,6 +38,7 @@ class SyncApiRepository {
|
||||
request.body = jsonEncode(
|
||||
SyncStreamDto(
|
||||
types: [
|
||||
SyncRequestType.authUsersV1,
|
||||
SyncRequestType.usersV1,
|
||||
SyncRequestType.assetsV1,
|
||||
SyncRequestType.assetExifsV1,
|
||||
@@ -69,6 +71,8 @@ class SyncApiRepository {
|
||||
shouldAbort = true;
|
||||
}
|
||||
|
||||
final reset = onReset ?? () {};
|
||||
|
||||
try {
|
||||
final response = await client.send(request);
|
||||
|
||||
@@ -91,12 +95,12 @@ class SyncApiRepository {
|
||||
continue;
|
||||
}
|
||||
|
||||
await onData(_parseLines(lines), abort);
|
||||
await onData(_parseLines(lines), abort, reset);
|
||||
lines.clear();
|
||||
}
|
||||
|
||||
if (lines.isNotEmpty && !shouldAbort) {
|
||||
await onData(_parseLines(lines), abort);
|
||||
await onData(_parseLines(lines), abort, reset);
|
||||
}
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Error processing stream", error, stack);
|
||||
@@ -130,6 +134,7 @@ class SyncApiRepository {
|
||||
}
|
||||
|
||||
const _kResponseMap = <SyncEntityType, Function(Object)>{
|
||||
SyncEntityType.authUserV1: SyncAuthUserV1.fromJson,
|
||||
SyncEntityType.userV1: SyncUserV1.fromJson,
|
||||
SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson,
|
||||
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
||||
@@ -156,7 +161,8 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
||||
SyncEntityType.albumToAssetV1: SyncAlbumToAssetV1.fromJson,
|
||||
SyncEntityType.albumToAssetBackfillV1: SyncAlbumToAssetV1.fromJson,
|
||||
SyncEntityType.albumToAssetDeleteV1: SyncAlbumToAssetDeleteV1.fromJson,
|
||||
SyncEntityType.syncAckV1: _SyncAckV1.fromJson,
|
||||
SyncEntityType.syncAckV1: _SyncEmptyDto.fromJson,
|
||||
SyncEntityType.syncResetV1: _SyncEmptyDto.fromJson,
|
||||
SyncEntityType.memoryV1: SyncMemoryV1.fromJson,
|
||||
SyncEntityType.memoryDeleteV1: SyncMemoryDeleteV1.fromJson,
|
||||
SyncEntityType.memoryToAssetV1: SyncMemoryAssetV1.fromJson,
|
||||
@@ -172,8 +178,9 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
||||
SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson,
|
||||
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
|
||||
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
|
||||
SyncEntityType.syncCompleteV1: _SyncEmptyDto.fromJson,
|
||||
};
|
||||
|
||||
class _SyncAckV1 {
|
||||
static _SyncAckV1? fromJson(dynamic _) => _SyncAckV1();
|
||||
class _SyncEmptyDto {
|
||||
static _SyncEmptyDto? fromJson(dynamic _) => _SyncEmptyDto();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/memory.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart';
|
||||
@@ -29,6 +32,65 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
|
||||
SyncStreamRepository(super.db) : _db = db;
|
||||
|
||||
Future<void> reset() async {
|
||||
_logger.fine("SyncResetV1 received. Resetting remote entities");
|
||||
try {
|
||||
await _db.exclusively(() async {
|
||||
// foreign_keys PRAGMA is no-op within transactions
|
||||
// https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
||||
await _db.customStatement('PRAGMA foreign_keys = OFF');
|
||||
await transaction(() async {
|
||||
await _db.assetFaceEntity.deleteAll();
|
||||
await _db.memoryAssetEntity.deleteAll();
|
||||
await _db.memoryEntity.deleteAll();
|
||||
await _db.partnerEntity.deleteAll();
|
||||
await _db.personEntity.deleteAll();
|
||||
await _db.remoteAlbumAssetEntity.deleteAll();
|
||||
await _db.remoteAlbumEntity.deleteAll();
|
||||
await _db.remoteAlbumUserEntity.deleteAll();
|
||||
await _db.remoteAssetEntity.deleteAll();
|
||||
await _db.remoteExifEntity.deleteAll();
|
||||
await _db.stackEntity.deleteAll();
|
||||
await _db.userEntity.deleteAll();
|
||||
await _db.userMetadataEntity.deleteAll();
|
||||
});
|
||||
await _db.customStatement('PRAGMA foreign_keys = ON');
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: SyncResetV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateAuthUsersV1(Iterable<SyncAuthUserV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
for (final user in data) {
|
||||
final companion = AuthUserEntityCompanion(
|
||||
name: Value(user.name),
|
||||
email: Value(user.email),
|
||||
hasProfileImage: Value(user.hasProfileImage),
|
||||
profileChangedAt: Value(user.profileChangedAt),
|
||||
avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary),
|
||||
isAdmin: Value(user.isAdmin),
|
||||
pinCode: Value(user.pinCode),
|
||||
quotaSizeInBytes: Value(user.quotaSizeInBytes ?? 0),
|
||||
quotaUsageInBytes: Value(user.quotaUsageInBytes),
|
||||
);
|
||||
|
||||
batch.insert(
|
||||
_db.authUserEntity,
|
||||
companion.copyWith(id: Value(user.id)),
|
||||
onConflict: DoUpdate((_) => companion),
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: SyncAuthUserV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
|
||||
try {
|
||||
await _db.userEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.userId)));
|
||||
@@ -47,6 +109,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
email: Value(user.email),
|
||||
hasProfileImage: Value(user.hasProfileImage),
|
||||
profileChangedAt: Value(user.profileChangedAt),
|
||||
avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary),
|
||||
);
|
||||
|
||||
batch.insert(_db.userEntity, companion.copyWith(id: Value(user.id)), onConflict: DoUpdate((_) => companion));
|
||||
@@ -573,3 +636,7 @@ extension on String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension on UserAvatarColor {
|
||||
AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == value);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
@@ -68,12 +68,12 @@ class IsarUserRepository extends IsarDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
class DriftUserRepository extends DriftDatabaseRepository {
|
||||
class DriftAuthUserRepository extends DriftDatabaseRepository {
|
||||
final Drift _db;
|
||||
const DriftUserRepository(super.db) : _db = db;
|
||||
const DriftAuthUserRepository(super.db) : _db = db;
|
||||
|
||||
Future<UserDto?> get(String id) async {
|
||||
final user = await _db.managers.userEntity.filter((user) => user.id.equals(id)).getSingleOrNull();
|
||||
final user = await _db.managers.authUserEntity.filter((user) => user.id.equals(id)).getSingleOrNull();
|
||||
|
||||
if (user == null) return null;
|
||||
|
||||
@@ -84,43 +84,30 @@ class DriftUserRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
|
||||
Future<UserDto> upsert(UserDto user) async {
|
||||
await _db.userEntity.insertOnConflictUpdate(
|
||||
UserEntityCompanion(
|
||||
await _db.authUserEntity.insertOnConflictUpdate(
|
||||
AuthUserEntityCompanion(
|
||||
id: Value(user.id),
|
||||
isAdmin: Value(user.isAdmin),
|
||||
updatedAt: Value(user.updatedAt),
|
||||
name: Value(user.name),
|
||||
email: Value(user.email),
|
||||
hasProfileImage: Value(user.hasProfileImage),
|
||||
profileChangedAt: Value(user.profileChangedAt),
|
||||
isAdmin: Value(user.isAdmin),
|
||||
quotaSizeInBytes: Value(user.quotaSizeInBytes),
|
||||
quotaUsageInBytes: Value(user.quotaUsageInBytes),
|
||||
avatarColor: Value(user.avatarColor),
|
||||
),
|
||||
);
|
||||
return user;
|
||||
}
|
||||
|
||||
Future<List<UserDto>> getAll() async {
|
||||
final users = await _db.userEntity.select().get();
|
||||
final List<UserDto> result = [];
|
||||
|
||||
for (final user in users) {
|
||||
final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(user.id));
|
||||
final metadata = await query.map((row) => row.toDto()).get();
|
||||
result.add(user.toDto(metadata));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
extension on UserEntityData {
|
||||
extension on AuthUserEntityData {
|
||||
UserDto toDto([List<UserMetadata>? metadata]) {
|
||||
AvatarColor avatarColor = AvatarColor.primary;
|
||||
bool memoryEnabled = true;
|
||||
|
||||
if (metadata != null) {
|
||||
for (final meta in metadata) {
|
||||
if (meta.key == UserMetadataKey.preferences && meta.preferences != null) {
|
||||
avatarColor = meta.preferences?.userAvatarColor ?? AvatarColor.primary;
|
||||
memoryEnabled = meta.preferences?.memoriesEnabled ?? true;
|
||||
}
|
||||
}
|
||||
@@ -130,12 +117,13 @@ extension on UserEntityData {
|
||||
id: id,
|
||||
email: email,
|
||||
name: name,
|
||||
isAdmin: isAdmin,
|
||||
updatedAt: updatedAt,
|
||||
profileChangedAt: profileChangedAt,
|
||||
hasProfileImage: hasProfileImage,
|
||||
avatarColor: avatarColor,
|
||||
memoryEnabled: memoryEnabled,
|
||||
isAdmin: isAdmin,
|
||||
quotaSizeInBytes: quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
// TODO: Move to repository once all classes are refactored
|
||||
|
||||
@@ -17,9 +17,9 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/db.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/locale_provider.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/providers/theme.provider.dart';
|
||||
@@ -205,9 +205,9 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
||||
// needs to be delayed so that EasyLocalization is working
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
ref.read(backgroundServiceProvider).disableService();
|
||||
ref.read(driftBackgroundUploadFgService).enable();
|
||||
ref.read(backgroundWorkerFgServiceProvider).enable();
|
||||
} else {
|
||||
ref.read(driftBackgroundUploadFgService).disable();
|
||||
ref.read(backgroundWorkerFgServiceProvider).disable();
|
||||
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
||||
import 'package:immich_mobile/providers/gallery_permission.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/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/services/background.service.dart';
|
||||
@@ -79,7 +80,7 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||
ref.read(readonlyModeProvider.notifier).setReadonlyMode(false);
|
||||
await migrateStoreToIsar(ref.read(isarProvider), ref.read(driftProvider));
|
||||
await ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||
await ref.read(driftBackgroundUploadFgService).disable();
|
||||
await ref.read(backgroundWorkerFgServiceProvider).disable();
|
||||
}
|
||||
|
||||
await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_se
|
||||
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/beta_sync_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/sync_status_and_actions.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart';
|
||||
import 'package:immich_mobile/widgets/settings/language_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart';
|
||||
@@ -20,7 +20,7 @@ import 'package:immich_mobile/widgets/settings/preference_settings/preference_se
|
||||
import 'package:immich_mobile/widgets/settings/settings_card.dart';
|
||||
|
||||
enum SettingSection {
|
||||
beta('beta_sync', Icons.sync_outlined, "beta_sync_subtitle"),
|
||||
beta('sync_status', Icons.sync_outlined, "sync_status_subtitle"),
|
||||
advanced('advanced', Icons.build_outlined, "advanced_settings_tile_subtitle"),
|
||||
assetViewer('asset_viewer_settings_title', Icons.image_outlined, "asset_viewer_settings_subtitle"),
|
||||
backup('backup', Icons.cloud_upload_outlined, "backup_settings_subtitle"),
|
||||
@@ -76,9 +76,9 @@ class _MobileLayout extends StatelessWidget {
|
||||
if (Store.isBetaTimelineEnabled)
|
||||
SettingsCard(
|
||||
icon: Icons.sync_outlined,
|
||||
title: 'beta_sync'.tr(),
|
||||
subtitle: 'beta_sync_subtitle'.tr(),
|
||||
settingRoute: const BetaSyncSettingsRoute(),
|
||||
title: 'sync_status'.tr(),
|
||||
subtitle: 'sync_status_subtitle'.tr(),
|
||||
settingRoute: const SyncStatusRoute(),
|
||||
),
|
||||
]
|
||||
: [
|
||||
@@ -143,7 +143,7 @@ class _BetaLandscapeToggle extends HookWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 100, child: BetaTimelineListTile()),
|
||||
if (Store.isBetaTimelineEnabled) const Expanded(child: BetaSyncSettings()),
|
||||
if (Store.isBetaTimelineEnabled) const Expanded(child: SyncStatusAndActions()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ 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/store.model.dart';
|
||||
import 'package:immich_mobile/domain/utils/isolate_lock_manager.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
@@ -23,23 +21,14 @@ class SplashScreenPage extends StatefulHookConsumerWidget {
|
||||
|
||||
class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
final log = Logger("SplashScreenPage");
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final lockManager = ref.read(isolateLockManagerProvider(kIsolateLockManagerPort));
|
||||
|
||||
lockManager.requestHolderToClose();
|
||||
lockManager
|
||||
.acquireLock()
|
||||
.timeout(const Duration(seconds: 5))
|
||||
.whenComplete(
|
||||
() => ref
|
||||
.read(authProvider.notifier)
|
||||
.setOpenApiServiceEndpoint()
|
||||
.then(logConnectionInfo)
|
||||
.whenComplete(() => resumeSession()),
|
||||
);
|
||||
ref
|
||||
.read(authProvider.notifier)
|
||||
.setOpenApiServiceEndpoint()
|
||||
.then(logConnectionInfo)
|
||||
.whenComplete(() => resumeSession());
|
||||
}
|
||||
|
||||
void logConnectionInfo(String? endpoint) {
|
||||
@@ -80,7 +69,16 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
// clean install - change the default of the flag
|
||||
// current install not using beta timeline
|
||||
if (context.router.current.name == SplashScreenRoute.name) {
|
||||
final needBetaMigration = Store.get(StoreKey.needBetaMigration, false);
|
||||
if (needBetaMigration) {
|
||||
await Store.put(StoreKey.needBetaMigration, false);
|
||||
context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]);
|
||||
return;
|
||||
}
|
||||
|
||||
context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/beta_sync_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/sync_status_and_actions.dart';
|
||||
|
||||
@RoutePage()
|
||||
class BetaSyncSettingsPage extends StatelessWidget {
|
||||
const BetaSyncSettingsPage({super.key});
|
||||
class SyncStatusPage extends StatelessWidget {
|
||||
const SyncStatusPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: const Text("beta_sync").t(context: context),
|
||||
title: const Text("sync_status").t(context: context),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(true),
|
||||
splashRadius: 24,
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: const BetaSyncSettings(),
|
||||
body: const SyncStatusAndActions(),
|
||||
);
|
||||
}
|
||||
}
|
||||
345
mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart
Normal file
345
mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart
Normal file
@@ -0,0 +1,345 @@
|
||||
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/exif.model.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AssetTroubleshootPage extends ConsumerWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const AssetTroubleshootPage({super.key, required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("Asset Troubleshoot")),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: _AssetDetailsView(asset: asset),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetDetailsView extends ConsumerWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const _AssetDetailsView({required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_AssetPropertiesSection(asset: asset),
|
||||
const SizedBox(height: 16),
|
||||
Text('Matching Assets', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
|
||||
if (asset.checksum != null) ...[
|
||||
_LocalAssetsSection(asset: asset),
|
||||
const SizedBox(height: 16),
|
||||
_RemoteAssetSection(asset: asset),
|
||||
] else ...[
|
||||
const _PropertySectionCard(
|
||||
title: 'Local Assets',
|
||||
properties: [_PropertyItem(label: 'Status', value: 'No checksum available - cannot fetch local assets')],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const _PropertySectionCard(
|
||||
title: 'Remote Assets',
|
||||
properties: [_PropertyItem(label: 'Status', value: 'No checksum available - cannot fetch remote asset')],
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetPropertiesSection extends ConsumerStatefulWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const _AssetPropertiesSection({required this.asset});
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _AssetPropertiesSectionState();
|
||||
}
|
||||
|
||||
class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection> {
|
||||
List<_PropertyItem> properties = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_buildAssetProperties(widget.asset).whenComplete(() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final title = _getAssetTypeTitle(widget.asset);
|
||||
|
||||
return _PropertySectionCard(title: title, properties: properties);
|
||||
}
|
||||
|
||||
Future<void> _buildAssetProperties(BaseAsset asset) async {
|
||||
_addCommonProperties();
|
||||
|
||||
if (asset is LocalAsset) {
|
||||
await _addLocalAssetProperties(asset);
|
||||
} else if (asset is RemoteAsset) {
|
||||
await _addRemoteAssetProperties(asset);
|
||||
}
|
||||
}
|
||||
|
||||
void _addCommonProperties() {
|
||||
final asset = widget.asset;
|
||||
properties.addAll([
|
||||
_PropertyItem(label: 'Name', value: asset.name),
|
||||
_PropertyItem(label: 'Checksum', value: asset.checksum),
|
||||
_PropertyItem(label: 'Type', value: asset.type.toString()),
|
||||
_PropertyItem(label: 'Created At', value: asset.createdAt.toString()),
|
||||
_PropertyItem(label: 'Updated At', value: asset.updatedAt.toString()),
|
||||
_PropertyItem(label: 'Width', value: asset.width?.toString()),
|
||||
_PropertyItem(label: 'Height', value: asset.height?.toString()),
|
||||
_PropertyItem(
|
||||
label: 'Duration',
|
||||
value: asset.durationInSeconds != null ? '${asset.durationInSeconds} seconds' : null,
|
||||
),
|
||||
_PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()),
|
||||
_PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> _addLocalAssetProperties(LocalAsset asset) async {
|
||||
properties.insertAll(0, [
|
||||
_PropertyItem(label: 'Local ID', value: asset.id),
|
||||
_PropertyItem(label: 'Remote ID', value: asset.remoteId),
|
||||
]);
|
||||
|
||||
properties.insert(4, _PropertyItem(label: 'Orientation', value: asset.orientation.toString()));
|
||||
final albums = await ref.read(assetServiceProvider).getSourceAlbums(asset.id);
|
||||
properties.add(_PropertyItem(label: 'Album', value: albums.map((a) => a.name).join(', ')));
|
||||
}
|
||||
|
||||
Future<void> _addRemoteAssetProperties(RemoteAsset asset) async {
|
||||
properties.insertAll(0, [
|
||||
_PropertyItem(label: 'Remote ID', value: asset.id),
|
||||
_PropertyItem(label: 'Local ID', value: asset.localId),
|
||||
_PropertyItem(label: 'Owner ID', value: asset.ownerId),
|
||||
]);
|
||||
|
||||
final additionalProps = <_PropertyItem>[
|
||||
_PropertyItem(label: 'Thumb Hash', value: asset.thumbHash),
|
||||
_PropertyItem(label: 'Visibility', value: asset.visibility.toString()),
|
||||
_PropertyItem(label: 'Stack ID', value: asset.stackId),
|
||||
];
|
||||
|
||||
properties.insertAll(4, additionalProps);
|
||||
|
||||
final exif = await ref.read(assetServiceProvider).getExif(asset);
|
||||
if (exif != null) {
|
||||
_addExifProperties(exif);
|
||||
} else {
|
||||
properties.add(const _PropertyItem(label: 'EXIF', value: null));
|
||||
}
|
||||
}
|
||||
|
||||
void _addExifProperties(ExifInfo exif) {
|
||||
properties.addAll([
|
||||
_PropertyItem(
|
||||
label: 'File Size',
|
||||
value: exif.fileSize != null ? '${(exif.fileSize! / 1024 / 1024).toStringAsFixed(2)} MB' : null,
|
||||
),
|
||||
_PropertyItem(label: 'Description', value: exif.description),
|
||||
_PropertyItem(label: 'EXIF Width', value: exif.width?.toString()),
|
||||
_PropertyItem(label: 'EXIF Height', value: exif.height?.toString()),
|
||||
_PropertyItem(label: 'Date Taken', value: exif.dateTimeOriginal?.toString()),
|
||||
_PropertyItem(label: 'Time Zone', value: exif.timeZone),
|
||||
_PropertyItem(label: 'Camera Make', value: exif.make),
|
||||
_PropertyItem(label: 'Camera Model', value: exif.model),
|
||||
_PropertyItem(label: 'Lens', value: exif.lens),
|
||||
_PropertyItem(label: 'F-Number', value: exif.f != null ? 'f/${exif.fNumber}' : null),
|
||||
_PropertyItem(label: 'Focal Length', value: exif.mm != null ? '${exif.focalLength}mm' : null),
|
||||
_PropertyItem(label: 'ISO', value: exif.iso?.toString()),
|
||||
_PropertyItem(label: 'Exposure Time', value: exif.exposureTime.isNotEmpty ? exif.exposureTime : null),
|
||||
_PropertyItem(
|
||||
label: 'GPS Coordinates',
|
||||
value: exif.hasCoordinates ? '${exif.latitude}, ${exif.longitude}' : null,
|
||||
),
|
||||
_PropertyItem(
|
||||
label: 'Location',
|
||||
value: [exif.city, exif.state, exif.country].where((e) => e != null && e.isNotEmpty).join(', '),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
String _getAssetTypeTitle(BaseAsset asset) {
|
||||
if (asset is LocalAsset) return 'Local Asset';
|
||||
if (asset is RemoteAsset) return 'Remote Asset';
|
||||
return 'Base Asset';
|
||||
}
|
||||
}
|
||||
|
||||
class _LocalAssetsSection extends ConsumerWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const _LocalAssetsSection({required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assetService = ref.watch(assetServiceProvider);
|
||||
|
||||
return FutureBuilder<List<LocalAsset?>>(
|
||||
future: assetService.getLocalAssetsByChecksum(asset.checksum!),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const _PropertySectionCard(
|
||||
title: 'Local Assets',
|
||||
properties: [_PropertyItem(label: 'Status', value: 'Loading...')],
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return _PropertySectionCard(
|
||||
title: 'Local Assets',
|
||||
properties: [_PropertyItem(label: 'Error', value: snapshot.error.toString())],
|
||||
);
|
||||
}
|
||||
|
||||
final localAssets = snapshot.data?.cast<LocalAsset>() ?? [];
|
||||
if (asset is LocalAsset) {
|
||||
localAssets.removeWhere((a) => a.id == (asset as LocalAsset).id);
|
||||
|
||||
if (localAssets.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
if (localAssets.isEmpty) {
|
||||
return const _PropertySectionCard(
|
||||
title: 'Local Assets',
|
||||
properties: [_PropertyItem(label: 'Status', value: 'No local assets found with this checksum')],
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (localAssets.length > 1)
|
||||
_PropertySectionCard(
|
||||
title: 'Local Assets Summary',
|
||||
properties: [_PropertyItem(label: 'Total Count', value: localAssets.length.toString())],
|
||||
),
|
||||
...localAssets.map((localAsset) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: _AssetPropertiesSection(asset: localAsset),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RemoteAssetSection extends ConsumerWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const _RemoteAssetSection({required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assetService = ref.watch(assetServiceProvider);
|
||||
|
||||
if (asset is RemoteAsset) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return FutureBuilder<RemoteAsset?>(
|
||||
future: assetService.getRemoteAssetByChecksum(asset.checksum!),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const _PropertySectionCard(
|
||||
title: 'Remote Assets',
|
||||
properties: [_PropertyItem(label: 'Status', value: 'Loading...')],
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return _PropertySectionCard(
|
||||
title: 'Remote Assets',
|
||||
properties: [_PropertyItem(label: 'Error', value: snapshot.error.toString())],
|
||||
);
|
||||
}
|
||||
|
||||
final remoteAsset = snapshot.data;
|
||||
|
||||
if (remoteAsset == null) {
|
||||
return const _PropertySectionCard(
|
||||
title: 'Remote Assets',
|
||||
properties: [_PropertyItem(label: 'Status', value: 'No remote asset found with this checksum')],
|
||||
);
|
||||
}
|
||||
|
||||
return _AssetPropertiesSection(asset: remoteAsset);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PropertySectionCard extends StatelessWidget {
|
||||
final String title;
|
||||
final List<_PropertyItem> properties;
|
||||
|
||||
const _PropertySectionCard({required this.title, required this.properties});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
...properties,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PropertyItem extends StatelessWidget {
|
||||
final String label;
|
||||
final String? value;
|
||||
|
||||
const _PropertyItem({required this.label, this.value});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text('$label:', style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(value ?? 'N/A', style: TextStyle(color: Theme.of(context).colorScheme.secondary)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.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/providers/infrastructure/db.provider.dart';
|
||||
@@ -26,11 +25,9 @@ final driftUsersProvider = FutureProvider.autoDispose<List<UserDto>>((ref) async
|
||||
id: entity.id,
|
||||
name: entity.name,
|
||||
email: entity.email,
|
||||
isAdmin: entity.isAdmin,
|
||||
updatedAt: entity.updatedAt,
|
||||
isPartnerSharedBy: false,
|
||||
isPartnerSharedWith: false,
|
||||
avatarColor: AvatarColor.primary,
|
||||
avatarColor: entity.avatarColor,
|
||||
memoryEnabled: true,
|
||||
inTimeline: true,
|
||||
profileChangedAt: entity.profileChangedAt,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
|
||||
class AdvancedInfoActionButton extends ConsumerWidget {
|
||||
final ActionSource source;
|
||||
|
||||
const AdvancedInfoActionButton({super.key, required this.source});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(actionProvider.notifier).troubleshoot(source, context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
maxWidth: 115.0,
|
||||
iconData: Icons.help_outline_rounded,
|
||||
label: "troubleshoot".t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_location_details.widget.dart';
|
||||
@@ -14,6 +15,7 @@ import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
@@ -41,6 +43,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
||||
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
|
||||
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
|
||||
|
||||
final buttonContext = ActionButtonContext(
|
||||
asset: asset,
|
||||
@@ -49,6 +52,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
isTrashEnabled: isTrashEnable,
|
||||
isInLockedView: isInLockedView,
|
||||
currentAlbum: currentAlbum,
|
||||
advancedTroubleshooting: advancedTroubleshooting,
|
||||
source: ActionSource.viewer,
|
||||
);
|
||||
|
||||
@@ -122,6 +126,10 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
return [fNumber, exposureTime, focalLength, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator);
|
||||
}
|
||||
|
||||
Future<void> _editDateTime(BuildContext context, WidgetRef ref) async {
|
||||
await ref.read(actionProvider.notifier).editDateTime(ActionSource.viewer, context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final asset = ref.watch(currentAssetNotifier);
|
||||
@@ -132,10 +140,6 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
final exifInfo = ref.watch(currentAssetExifProvider).valueOrNull;
|
||||
final cameraTitle = _getCameraInfoTitle(exifInfo);
|
||||
|
||||
Future<void> editDateTime() async {
|
||||
await ref.read(actionProvider.notifier).editDateTime(ActionSource.viewer, context);
|
||||
}
|
||||
|
||||
return SliverList.list(
|
||||
children: [
|
||||
// Asset Date and Time
|
||||
@@ -143,7 +147,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
title: _getDateTime(context, asset),
|
||||
titleStyle: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
trailing: asset.hasRemote ? const Icon(Icons.edit, size: 18) : null,
|
||||
onTap: asset.hasRemote ? () async => await editDateTime() : null,
|
||||
onTap: asset.hasRemote ? () async => await _editDateTime(context, ref) : null,
|
||||
),
|
||||
if (exifInfo != null) _SheetAssetDescription(exif: exifInfo),
|
||||
const SheetPeopleDetails(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart';
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
|
||||
@@ -21,6 +23,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
@@ -51,6 +54,7 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
||||
Widget build(BuildContext context) {
|
||||
final multiselect = ref.watch(multiSelectProvider);
|
||||
final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash));
|
||||
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
|
||||
|
||||
Future<void> addAssetsToAlbum(RemoteAlbum album) async {
|
||||
final selectedAssets = multiselect.selectedAssets;
|
||||
@@ -88,6 +92,9 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
||||
maxChildSize: 0.85,
|
||||
shouldCloseOnMinExtent: false,
|
||||
actions: [
|
||||
if (multiselect.selectedAssets.length == 1 && advancedTroubleshooting) ...[
|
||||
const AdvancedInfoActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
const ShareActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasRemote) ...[
|
||||
const ShareLinkActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||
import 'package:immich_mobile/domain/utils/isolate_lock_manager.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
@@ -125,82 +124,43 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _safeRun(Future<void> action, String debugName) async {
|
||||
if (!_shouldContinueOperation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await action;
|
||||
} catch (e, stackTrace) {
|
||||
_log.warning("Error during $debugName operation", e, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleBetaTimelineResume() async {
|
||||
_ref.read(backupProvider.notifier).cancelBackup();
|
||||
final lockManager = _ref.read(isolateLockManagerProvider(kIsolateLockManagerPort));
|
||||
|
||||
// Give isolates time to complete any ongoing database transactions
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
lockManager.requestHolderToClose();
|
||||
|
||||
// Add timeout to prevent deadlock on lock acquisition
|
||||
try {
|
||||
await lockManager.acquireLock().timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
_log.warning("Lock acquisition timed out, proceeding without lock");
|
||||
throw TimeoutException("Lock acquisition timed out", const Duration(seconds: 10));
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_log.warning("Failed to acquire lock: $e");
|
||||
return;
|
||||
}
|
||||
|
||||
final backgroundManager = _ref.read(backgroundSyncProvider);
|
||||
final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
|
||||
try {
|
||||
// Run operations sequentially with state checks and error handling for each
|
||||
if (_shouldContinueOperation()) {
|
||||
try {
|
||||
await backgroundManager.syncLocal();
|
||||
} catch (e, stackTrace) {
|
||||
_log.warning("Failed syncLocal: $e", e, stackTrace);
|
||||
_safeRun(backgroundManager.syncLocal(), "syncLocal");
|
||||
_safeRun(backgroundManager.hashAssets(), "hashAssets");
|
||||
_safeRun(backgroundManager.syncRemote(), "syncRemote").then((_) {
|
||||
if (isAlbumLinkedSyncEnable) {
|
||||
_safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if app is still active before hashing
|
||||
if (_shouldContinueOperation()) {
|
||||
try {
|
||||
await backgroundManager.hashAssets();
|
||||
} catch (e, stackTrace) {
|
||||
_log.warning("Failed hashAssets: $e", e, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if app is still active before remote sync
|
||||
if (_shouldContinueOperation()) {
|
||||
try {
|
||||
await backgroundManager.syncRemote();
|
||||
} catch (e, stackTrace) {
|
||||
_log.warning("Failed syncRemote: $e", e, stackTrace);
|
||||
}
|
||||
|
||||
if (isAlbumLinkedSyncEnable && _shouldContinueOperation()) {
|
||||
try {
|
||||
await backgroundManager.syncLinkedAlbum();
|
||||
} catch (e, stackTrace) {
|
||||
_log.warning("Failed syncLinkedAlbum: $e", e, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle backup resume only if still active
|
||||
if (_shouldContinueOperation()) {
|
||||
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
|
||||
if (isEnableBackup) {
|
||||
final currentUser = _ref.read(currentUserProvider);
|
||||
if (currentUser != null) {
|
||||
try {
|
||||
await _ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
|
||||
_log.fine("Completed backup resume");
|
||||
} catch (e, stackTrace) {
|
||||
_log.warning("Failed backup resume: $e", e, stackTrace);
|
||||
}
|
||||
}
|
||||
if (isEnableBackup) {
|
||||
final currentUser = _ref.read(currentUserProvider);
|
||||
if (currentUser != null) {
|
||||
_safeRun(_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id), "handleBackupResume");
|
||||
}
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
@@ -208,8 +168,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
} finally {
|
||||
// Ensure lock is released even if operations fail
|
||||
try {
|
||||
lockManager.releaseLock();
|
||||
_log.fine("Lock released after background sync operations");
|
||||
_log.info("Lock released after background sync operations");
|
||||
} catch (lockError) {
|
||||
_log.warning("Failed to release lock after error: $lockError");
|
||||
}
|
||||
@@ -263,28 +222,6 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
if (_ref.read(backupProvider.notifier).backupProgress != BackUpProgressEnum.manualInProgress) {
|
||||
_ref.read(backupProvider.notifier).cancelBackup();
|
||||
}
|
||||
} else {
|
||||
final backgroundManager = _ref.read(backgroundSyncProvider);
|
||||
|
||||
// Cancel operations with extended timeout to allow database transactions to complete
|
||||
try {
|
||||
await Future.wait([
|
||||
backgroundManager.cancel().timeout(const Duration(seconds: 10)),
|
||||
backgroundManager.cancelLocal().timeout(const Duration(seconds: 10)),
|
||||
]).timeout(const Duration(seconds: 15));
|
||||
|
||||
// Give additional time for isolates to clean up database connections
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
} catch (e) {
|
||||
_log.warning("Timeout during background cancellation: $e");
|
||||
}
|
||||
|
||||
// Always release the lock, even if cancellation failed
|
||||
try {
|
||||
_ref.read(isolateLockManagerProvider(kIsolateLockManagerPort)).releaseLock();
|
||||
} catch (e) {
|
||||
_log.warning("Failed to release lock on pause: $e");
|
||||
}
|
||||
}
|
||||
|
||||
_ref.read(websocketProvider.notifier).disconnect();
|
||||
@@ -312,7 +249,6 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
} catch (_) {}
|
||||
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
_ref.read(isolateLockManagerProvider(kIsolateLockManagerPort)).releaseLock();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/domain/utils/isolate_lock_manager.dart';
|
||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||
|
||||
final backgroundSyncProvider = Provider<BackgroundSyncManager>((ref) {
|
||||
@@ -19,7 +18,3 @@ final backgroundSyncProvider = Provider<BackgroundSyncManager>((ref) {
|
||||
ref.onDispose(manager.cancel);
|
||||
return manager;
|
||||
});
|
||||
|
||||
final isolateLockManagerProvider = Provider.family<IsolateLockManager, String>((ref, name) {
|
||||
return IsolateLockManager(portName: name);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
@@ -18,7 +17,6 @@ import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
|
||||
@@ -36,8 +34,6 @@ import 'package:logging/logging.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
|
||||
|
||||
final driftBackgroundUploadFgService = Provider((ref) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
||||
|
||||
final backupProvider = StateNotifierProvider<BackupNotifier, BackUpState>((ref) {
|
||||
return BackupNotifier(
|
||||
ref.watch(backupServiceProvider),
|
||||
|
||||
@@ -6,11 +6,11 @@ import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -380,5 +380,5 @@ final driftCandidateBackupAlbumInfoProvider = FutureProvider.autoDispose.family<
|
||||
ref,
|
||||
assetId,
|
||||
) {
|
||||
return ref.read(backupRepositoryProvider).getSourceAlbums(assetId);
|
||||
return ref.read(localAssetRepository).getSourceAlbums(assetId, backupSelection: BackupSelection.selected);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
@@ -6,6 +7,7 @@ import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/action.service.dart';
|
||||
import 'package:immich_mobile/services/download.service.dart';
|
||||
import 'package:immich_mobile/services/timeline.service.dart';
|
||||
@@ -115,6 +117,16 @@ class ActionNotifier extends Notifier<void> {
|
||||
};
|
||||
}
|
||||
|
||||
Future<ActionResult> troubleshoot(ActionSource source, BuildContext context) async {
|
||||
final assets = _getAssets(source);
|
||||
if (assets.length > 1) {
|
||||
return ActionResult(count: assets.length, success: false, error: 'Cannot troubleshoot multiple assets');
|
||||
}
|
||||
context.pushRoute(AssetTroubleshootRoute(asset: assets.first));
|
||||
|
||||
return ActionResult(count: assets.length, success: true);
|
||||
}
|
||||
|
||||
Future<ActionResult> shareLink(ActionSource source, BuildContext context) async {
|
||||
final ids = _getRemoteIdsForSource(source);
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
import 'package:immich_mobile/platform/thumbnail_api.g.dart';
|
||||
|
||||
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
||||
|
||||
final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
||||
|
||||
final thumbnailApi = ThumbnailApi();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
@@ -10,6 +9,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
@@ -25,25 +25,7 @@ class AuthRepository extends DatabaseRepository {
|
||||
const AuthRepository(super.db, this._drift);
|
||||
|
||||
Future<void> clearLocalData() async {
|
||||
// Drift deletions - child entities first (those with foreign keys)
|
||||
await Future.wait([
|
||||
_drift.memoryAssetEntity.deleteAll(),
|
||||
_drift.remoteAlbumAssetEntity.deleteAll(),
|
||||
_drift.remoteAlbumUserEntity.deleteAll(),
|
||||
_drift.remoteExifEntity.deleteAll(),
|
||||
_drift.userMetadataEntity.deleteAll(),
|
||||
_drift.partnerEntity.deleteAll(),
|
||||
_drift.stackEntity.deleteAll(),
|
||||
_drift.assetFaceEntity.deleteAll(),
|
||||
]);
|
||||
// Drift deletions - parent entities
|
||||
await Future.wait([
|
||||
_drift.memoryEntity.deleteAll(),
|
||||
_drift.personEntity.deleteAll(),
|
||||
_drift.remoteAlbumEntity.deleteAll(),
|
||||
_drift.remoteAssetEntity.deleteAll(),
|
||||
_drift.userEntity.deleteAll(),
|
||||
]);
|
||||
await SyncStreamRepository(_drift).reset();
|
||||
|
||||
return db.writeTxn(() {
|
||||
return Future.wait([
|
||||
|
||||
@@ -22,14 +22,14 @@ class PartnerApiRepository extends ApiRepository {
|
||||
}
|
||||
|
||||
Future<UserDto> create(String id) async {
|
||||
final dto = await checkNull(_api.createPartner(id));
|
||||
final dto = await checkNull(_api.createPartnerDeprecated(id));
|
||||
return UserConverter.fromPartnerDto(dto);
|
||||
}
|
||||
|
||||
Future<void> delete(String id) => _api.removePartner(id);
|
||||
|
||||
Future<UserDto> update(String id, {required bool inTimeline}) async {
|
||||
final dto = await checkNull(_api.updatePartner(id, UpdatePartnerDto(inTimeline: inTimeline)));
|
||||
final dto = await checkNull(_api.updatePartner(id, PartnerUpdateDto(inTimeline: inTimeline)));
|
||||
return UserConverter.fromPartnerDto(dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ import 'package:immich_mobile/pages/search/map/map_location_picker.page.dart';
|
||||
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/settings/beta_sync_settings.page.dart';
|
||||
import 'package:immich_mobile/pages/settings/sync_status.page.dart';
|
||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
|
||||
@@ -86,6 +86,7 @@ import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_album_options.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_archive.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_asset_selection_timeline.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_asset_troubleshoot.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_create_album.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_favorite.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_library.page.dart';
|
||||
@@ -332,7 +333,7 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: ChangeExperienceRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftPartnerRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftUploadDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: BetaSyncSettingsRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: SyncStatusRoute.page, guards: [_duplicateGuard]),
|
||||
AutoRoute(page: DriftPeopleCollectionRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftPersonRoute.page, guards: [_authGuard]),
|
||||
AutoRoute(page: DriftBackupOptionsRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
@@ -343,6 +344,7 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: DriftFilterImageRoute.page),
|
||||
AutoRoute(page: DriftActivitiesRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftBackupAssetDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
// required to handle all deeplinks in deep_link.service.dart
|
||||
// auto_route_library#1722
|
||||
RedirectRoute(path: '*', redirectTo: '/'),
|
||||
|
||||
@@ -403,6 +403,43 @@ class ArchiveRoute extends PageRouteInfo<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [AssetTroubleshootPage]
|
||||
class AssetTroubleshootRoute extends PageRouteInfo<AssetTroubleshootRouteArgs> {
|
||||
AssetTroubleshootRoute({
|
||||
Key? key,
|
||||
required BaseAsset asset,
|
||||
List<PageRouteInfo>? children,
|
||||
}) : super(
|
||||
AssetTroubleshootRoute.name,
|
||||
args: AssetTroubleshootRouteArgs(key: key, asset: asset),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'AssetTroubleshootRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<AssetTroubleshootRouteArgs>();
|
||||
return AssetTroubleshootPage(key: args.key, asset: args.asset);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class AssetTroubleshootRouteArgs {
|
||||
const AssetTroubleshootRouteArgs({this.key, required this.asset});
|
||||
|
||||
final Key? key;
|
||||
|
||||
final BaseAsset asset;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AssetTroubleshootRouteArgs{key: $key, asset: $asset}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [AssetViewerPage]
|
||||
class AssetViewerRoute extends PageRouteInfo<AssetViewerRouteArgs> {
|
||||
@@ -509,22 +546,6 @@ class BackupOptionsRoute extends PageRouteInfo<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [BetaSyncSettingsPage]
|
||||
class BetaSyncSettingsRoute extends PageRouteInfo<void> {
|
||||
const BetaSyncSettingsRoute({List<PageRouteInfo>? children})
|
||||
: super(BetaSyncSettingsRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'BetaSyncSettingsRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const BetaSyncSettingsPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [ChangeExperiencePage]
|
||||
class ChangeExperienceRoute extends PageRouteInfo<ChangeExperienceRouteArgs> {
|
||||
@@ -2629,6 +2650,22 @@ class SplashScreenRoute extends PageRouteInfo<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [SyncStatusPage]
|
||||
class SyncStatusRoute extends PageRouteInfo<void> {
|
||||
const SyncStatusRoute({List<PageRouteInfo>? children})
|
||||
: super(SyncStatusRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'SyncStatusRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const SyncStatusPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [TabControllerPage]
|
||||
class TabControllerRoute extends PageRouteInfo<void> {
|
||||
|
||||
@@ -46,7 +46,7 @@ enum AppSettingsEnum<T> {
|
||||
syncAlbums<bool>(StoreKey.syncAlbums, null, false),
|
||||
autoEndpointSwitching<bool>(StoreKey.autoEndpointSwitching, null, false),
|
||||
photoManagerCustomFilter<bool>(StoreKey.photoManagerCustomFilter, null, true),
|
||||
betaTimeline<bool>(StoreKey.betaTimeline, null, false),
|
||||
betaTimeline<bool>(StoreKey.betaTimeline, null, true),
|
||||
enableBackup<bool>(StoreKey.enableBackup, null, false),
|
||||
useCellularForUploadVideos<bool>(StoreKey.useWifiForUploadVideos, null, false),
|
||||
useCellularForUploadPhotos<bool>(StoreKey.useWifiForUploadPhotos, null, false),
|
||||
|
||||
@@ -147,7 +147,9 @@ class SyncService {
|
||||
dbUsers,
|
||||
compare: (UserDto a, UserDto b) => a.id.compareTo(b.id),
|
||||
both: (UserDto a, UserDto b) {
|
||||
if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) ||
|
||||
if ((a.updatedAt == null && b.updatedAt != null) ||
|
||||
(a.updatedAt != null && b.updatedAt == null) ||
|
||||
(a.updatedAt != null && b.updatedAt != null && !a.updatedAt!.isAtSameMomentAs(b.updatedAt!)) ||
|
||||
a.isPartnerSharedBy != b.isPartnerSharedBy ||
|
||||
a.isPartnerSharedWith != b.isPartnerSharedWith ||
|
||||
a.inTimeline != b.inTimeline) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
|
||||
@@ -15,7 +17,6 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
|
||||
class ActionButtonContext {
|
||||
final BaseAsset asset;
|
||||
@@ -24,6 +25,7 @@ class ActionButtonContext {
|
||||
final bool isTrashEnabled;
|
||||
final bool isInLockedView;
|
||||
final RemoteAlbum? currentAlbum;
|
||||
final bool advancedTroubleshooting;
|
||||
final ActionSource source;
|
||||
|
||||
const ActionButtonContext({
|
||||
@@ -33,11 +35,13 @@ class ActionButtonContext {
|
||||
required this.isTrashEnabled,
|
||||
required this.isInLockedView,
|
||||
required this.currentAlbum,
|
||||
required this.advancedTroubleshooting,
|
||||
required this.source,
|
||||
});
|
||||
}
|
||||
|
||||
enum ActionButtonType {
|
||||
advancedInfo,
|
||||
share,
|
||||
shareLink,
|
||||
archive,
|
||||
@@ -55,6 +59,7 @@ enum ActionButtonType {
|
||||
|
||||
bool shouldShow(ActionButtonContext context) {
|
||||
return switch (this) {
|
||||
ActionButtonType.advancedInfo => context.advancedTroubleshooting,
|
||||
ActionButtonType.share => true,
|
||||
ActionButtonType.shareLink =>
|
||||
!context.isInLockedView && //
|
||||
@@ -115,6 +120,7 @@ enum ActionButtonType {
|
||||
|
||||
Widget buildButton(ActionButtonContext context) {
|
||||
return switch (this) {
|
||||
ActionButtonType.advancedInfo => AdvancedInfoActionButton(source: context.source),
|
||||
ActionButtonType.share => ShareActionButton(source: context.source),
|
||||
ActionButtonType.shareLink => ShareLinkActionButton(source: context.source),
|
||||
ActionButtonType.archive => ArchiveActionButton(source: context.source),
|
||||
@@ -138,6 +144,7 @@ enum ActionButtonType {
|
||||
|
||||
class ActionButtonBuilder {
|
||||
static const List<ActionButtonType> _actionTypes = [
|
||||
ActionButtonType.advancedInfo,
|
||||
ActionButtonType.share,
|
||||
ActionButtonType.shareLink,
|
||||
ActionButtonType.likeActivity,
|
||||
|
||||
@@ -90,7 +90,7 @@ abstract final class Bootstrap {
|
||||
}
|
||||
|
||||
static Future<void> initDomain(Isar db, Drift drift, DriftLogger logDb, {bool shouldBufferLogs = true}) async {
|
||||
final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? false;
|
||||
final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? true;
|
||||
final IStoreRepository storeRepo = isBeta ? DriftStoreRepository(drift) : IsarStoreRepository(db);
|
||||
|
||||
await StoreService.init(storeRepository: storeRepo);
|
||||
|
||||
@@ -33,12 +33,11 @@ import 'package:logging/logging.dart';
|
||||
// ignore: import_rule_photo_manager
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
const int targetVersion = 14;
|
||||
const int targetVersion = 15;
|
||||
|
||||
Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
||||
final hasVersion = Store.tryGet(StoreKey.version) != null;
|
||||
final int version = Store.get(StoreKey.version, targetVersion);
|
||||
|
||||
if (version < 9) {
|
||||
await Store.put(StoreKey.version, targetVersion);
|
||||
final value = await db.storeValues.get(StoreKey.currentUser.id);
|
||||
@@ -68,6 +67,22 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
||||
await Store.populateCache();
|
||||
}
|
||||
|
||||
// Handle migration only for this version
|
||||
// TODO: remove when old timeline is removed
|
||||
if (version == 15) {
|
||||
final isBeta = Store.tryGet(StoreKey.betaTimeline);
|
||||
final isNewInstallation = await _isNewInstallation(db, drift);
|
||||
|
||||
// For new installations, no migration needed
|
||||
// For existing installations, only migrate if beta timeline is not enabled (null or false)
|
||||
if (isNewInstallation || isBeta == true) {
|
||||
await Store.put(StoreKey.needBetaMigration, false);
|
||||
} else {
|
||||
await resetDriftDatabase(drift);
|
||||
await Store.put(StoreKey.needBetaMigration, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetVersion >= 12) {
|
||||
await Store.put(StoreKey.version, targetVersion);
|
||||
return;
|
||||
@@ -80,6 +95,35 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _isNewInstallation(Isar db, Drift drift) async {
|
||||
try {
|
||||
final isarUserCount = await db.users.count();
|
||||
if (isarUserCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final isarAssetCount = await db.assets.count();
|
||||
if (isarAssetCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final driftStoreCount = await drift.storeEntity.select().get().then((list) => list.length);
|
||||
if (driftStoreCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final driftAssetCount = await drift.localAssetEntity.select().get().then((list) => list.length);
|
||||
if (driftAssetCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
debugPrint("[MIGRATION] Error checking if new installation: $error");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _migrateTo(Isar db, int version) async {
|
||||
await Store.delete(StoreKey.assetETag);
|
||||
await db.writeTxn(() async {
|
||||
@@ -284,3 +328,27 @@ Future<List<void>> runNewSync(WidgetRef ref, {bool full = false}) {
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> resetDriftDatabase(Drift drift) async {
|
||||
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
|
||||
final database = drift.attachedDatabase;
|
||||
await database.exclusively(() async {
|
||||
// https://stackoverflow.com/a/65743498/25690041
|
||||
await database.customStatement('PRAGMA writable_schema = 1;');
|
||||
await database.customStatement('DELETE FROM sqlite_master;');
|
||||
await database.customStatement('VACUUM;');
|
||||
await database.customStatement('PRAGMA writable_schema = 0;');
|
||||
await database.customStatement('PRAGMA integrity_check');
|
||||
|
||||
await database.customStatement('PRAGMA user_version = 0');
|
||||
await database.beforeOpen(
|
||||
// ignore: invalid_use_of_internal_member
|
||||
database.resolvedEngine.executor,
|
||||
OpeningDetails(null, database.schemaVersion),
|
||||
);
|
||||
await database.customStatement('PRAGMA user_version = ${database.schemaVersion}');
|
||||
|
||||
// Refresh all stream queries
|
||||
database.notifyUpdates({for (final table in database.allTables) TableUpdate.onTable(table)});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -90,11 +90,11 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
|
||||
minLeadingWidth: 50,
|
||||
leading: GestureDetector(
|
||||
onTap: pickUserProfileImage,
|
||||
onDoubleTap: toggleReadonlyMode,
|
||||
onLongPress: toggleReadonlyMode,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
buildUserProfileImage(),
|
||||
AbsorbPointer(child: buildUserProfileImage()),
|
||||
if (!isReadonlyModeEnabled)
|
||||
Positioned(
|
||||
bottom: -5,
|
||||
|
||||
@@ -157,7 +157,7 @@ class _ProfileIndicator extends ConsumerWidget {
|
||||
|
||||
return InkWell(
|
||||
onTap: () => showDialog(context: context, useRootNavigator: false, builder: (ctx) => const ImmichAppBarDialog()),
|
||||
onDoubleTap: () => toggleReadonlyMode(),
|
||||
onLongPress: () => toggleReadonlyMode(),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
child: Badge(
|
||||
label: Container(
|
||||
@@ -173,7 +173,7 @@ class _ProfileIndicator extends ConsumerWidget {
|
||||
? const Icon(Icons.face_outlined, size: widgetSize)
|
||||
: Semantics(
|
||||
label: "logged_in_as".tr(namedArgs: {"user": user.name}),
|
||||
child: UserCircleAvatar(radius: 17, size: 31, user: user),
|
||||
child: AbsorbPointer(child: UserCircleAvatar(radius: 17, size: 31, user: user)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,376 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart' as drift_db;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.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/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class BetaSyncSettings extends HookConsumerWidget {
|
||||
const BetaSyncSettings({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assetService = ref.watch(assetServiceProvider);
|
||||
final localAlbumService = ref.watch(localAlbumServiceProvider);
|
||||
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
|
||||
final memoryService = ref.watch(driftMemoryServiceProvider);
|
||||
|
||||
Future<List<dynamic>> loadCounts() async {
|
||||
final assetCounts = assetService.getAssetCounts();
|
||||
final localAlbumCounts = localAlbumService.getCount();
|
||||
final remoteAlbumCounts = remoteAlbumService.getCount();
|
||||
final memoryCount = memoryService.getCount();
|
||||
final getLocalHashedCount = assetService.getLocalHashedCount();
|
||||
|
||||
return await Future.wait([assetCounts, localAlbumCounts, remoteAlbumCounts, memoryCount, getLocalHashedCount]);
|
||||
}
|
||||
|
||||
Future<void> resetDatabase() async {
|
||||
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
|
||||
final drift = ref.read(driftProvider);
|
||||
final database = drift.attachedDatabase;
|
||||
await database.exclusively(() async {
|
||||
// https://stackoverflow.com/a/65743498/25690041
|
||||
await database.customStatement('PRAGMA writable_schema = 1;');
|
||||
await database.customStatement('DELETE FROM sqlite_master;');
|
||||
await database.customStatement('VACUUM;');
|
||||
await database.customStatement('PRAGMA writable_schema = 0;');
|
||||
await database.customStatement('PRAGMA integrity_check');
|
||||
|
||||
await database.customStatement('PRAGMA user_version = 0');
|
||||
await database.beforeOpen(
|
||||
// ignore: invalid_use_of_internal_member
|
||||
database.resolvedEngine.executor,
|
||||
drift_db.OpeningDetails(null, database.schemaVersion),
|
||||
);
|
||||
await database.customStatement('PRAGMA user_version = ${database.schemaVersion}');
|
||||
|
||||
// Refresh all stream queries
|
||||
database.notifyUpdates({for (final table in database.allTables) drift_db.TableUpdate.onTable(table)});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> exportDatabase() async {
|
||||
try {
|
||||
// WAL Checkpoint to ensure all changes are written to the database
|
||||
await ref.read(driftProvider).customStatement("pragma wal_checkpoint(truncate)");
|
||||
final documentsDir = await getApplicationDocumentsDirectory();
|
||||
final dbFile = File(path.join(documentsDir.path, 'immich.sqlite'));
|
||||
|
||||
if (!await dbFile.exists()) {
|
||||
if (context.mounted) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("Database file not found".t(context: context))),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final exportFile = File(path.join(documentsDir.path, 'immich_export_$timestamp.sqlite'));
|
||||
|
||||
await dbFile.copy(exportFile.path);
|
||||
|
||||
await Share.shareXFiles([XFile(exportFile.path)], text: 'Immich Database Export');
|
||||
|
||||
Future.delayed(const Duration(seconds: 30), () async {
|
||||
if (await exportFile.exists()) {
|
||||
await exportFile.delete();
|
||||
}
|
||||
});
|
||||
|
||||
if (context.mounted) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("Database exported successfully".t(context: context))),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("Failed to export database: $e".t(context: context))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearFileCache() async {
|
||||
await ref.read(storageRepositoryProvider).clearCache();
|
||||
}
|
||||
|
||||
Future<void> resetSqliteDb(BuildContext context, Future<void> Function() resetDatabase) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text("reset_sqlite".t(context: context)),
|
||||
content: Text("reset_sqlite_confirmation".t(context: context)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text("cancel".t(context: context)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await resetDatabase();
|
||||
context.pop();
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("reset_sqlite_success".t(context: context))),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
"confirm".t(context: context),
|
||||
style: TextStyle(color: context.colorScheme.error),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return FutureBuilder<List<dynamic>>(
|
||||
future: loadCounts(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Error occur, reset the local database by tapping the button below",
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
ListTile(
|
||||
title: Text(
|
||||
"reset_sqlite".t(context: context),
|
||||
style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error),
|
||||
onTap: () async {
|
||||
await resetSqliteDb(context, resetDatabase);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final assetCounts = snapshot.data![0]! as (int, int);
|
||||
final localAssetCount = assetCounts.$1;
|
||||
final remoteAssetCount = assetCounts.$2;
|
||||
|
||||
final localAlbumCount = snapshot.data![1]! as int;
|
||||
final remoteAlbumCount = snapshot.data![2]! as int;
|
||||
final memoryCount = snapshot.data![3]! as int;
|
||||
final localHashedCount = snapshot.data![4]! as int;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 16, bottom: 32),
|
||||
child: ListView(
|
||||
children: [
|
||||
_SectionHeaderText(text: "assets".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAssetCount,
|
||||
icon: Icons.smartphone,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAssetCount,
|
||||
icon: Icons.cloud,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_SectionHeaderText(text: "albums".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAlbumCount,
|
||||
icon: Icons.smartphone,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAlbumCount,
|
||||
icon: Icons.cloud,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_SectionHeaderText(text: "other".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "memories".t(context: context),
|
||||
count: memoryCount,
|
||||
icon: Icons.calendar_today,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "hashed_assets".t(context: context),
|
||||
count: localHashedCount,
|
||||
icon: Icons.tag,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
const SizedBox(height: 24),
|
||||
_SectionHeaderText(text: "jobs".t(context: context)),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"sync_local".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text("tap_to_run_job".t(context: context)),
|
||||
leading: const Icon(Icons.sync),
|
||||
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus),
|
||||
onTap: () {
|
||||
ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"sync_remote".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text("tap_to_run_job".t(context: context)),
|
||||
leading: const Icon(Icons.cloud_sync),
|
||||
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus),
|
||||
onTap: () {
|
||||
ref.read(backgroundSyncProvider).syncRemote();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"hash_asset".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: const Icon(Icons.tag),
|
||||
subtitle: Text("tap_to_run_job".t(context: context)),
|
||||
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus),
|
||||
onTap: () {
|
||||
ref.read(backgroundSyncProvider).hashAssets();
|
||||
},
|
||||
),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
const SizedBox(height: 24),
|
||||
_SectionHeaderText(text: "actions".t(context: context)),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"clear_file_cache".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: const Icon(Icons.playlist_remove_rounded),
|
||||
onTap: clearFileCache,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"export_database".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text("export_database_description".t(context: context)),
|
||||
leading: const Icon(Icons.download),
|
||||
onTap: exportDatabase,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"reset_sqlite".t(context: context),
|
||||
style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error),
|
||||
onTap: () async {
|
||||
await resetSqliteDb(context, resetDatabase);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SyncStatusIcon extends StatelessWidget {
|
||||
final SyncStatus status;
|
||||
|
||||
const _SyncStatusIcon({required this.status});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (status) {
|
||||
SyncStatus.idle => const Icon(Icons.pause_circle_outline_rounded),
|
||||
SyncStatus.syncing => const SizedBox(height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
SyncStatus.success => const Icon(Icons.check_circle_outline, color: Colors.green),
|
||||
SyncStatus.error => Icon(Icons.error_outline, color: context.colorScheme.error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeaderText extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const _SectionHeaderText({required this.text});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Text(
|
||||
text.toUpperCase(),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: context.colorScheme.onSurface.withAlpha(200),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.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/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||
import 'package:immich_mobile/utils/migration.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class SyncStatusAndActions extends HookConsumerWidget {
|
||||
const SyncStatusAndActions({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Future<void> exportDatabase() async {
|
||||
try {
|
||||
// WAL Checkpoint to ensure all changes are written to the database
|
||||
await ref.read(driftProvider).customStatement("pragma wal_checkpoint(truncate)");
|
||||
final documentsDir = await getApplicationDocumentsDirectory();
|
||||
final dbFile = File(path.join(documentsDir.path, 'immich.sqlite'));
|
||||
|
||||
if (!await dbFile.exists()) {
|
||||
if (context.mounted) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("Database file not found".t(context: context))),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final exportFile = File(path.join(documentsDir.path, 'immich_export_$timestamp.sqlite'));
|
||||
|
||||
await dbFile.copy(exportFile.path);
|
||||
|
||||
await Share.shareXFiles([XFile(exportFile.path)], text: 'Immich Database Export');
|
||||
|
||||
Future.delayed(const Duration(seconds: 30), () async {
|
||||
if (await exportFile.exists()) {
|
||||
await exportFile.delete();
|
||||
}
|
||||
});
|
||||
|
||||
if (context.mounted) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("Database exported successfully".t(context: context))),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("Failed to export database: $e".t(context: context))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearFileCache() async {
|
||||
await ref.read(storageRepositoryProvider).clearCache();
|
||||
}
|
||||
|
||||
Future<void> resetSqliteDb(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text("reset_sqlite".t(context: context)),
|
||||
content: Text("reset_sqlite_confirmation".t(context: context)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text("cancel".t(context: context)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await resetDriftDatabase(ref.read(driftProvider));
|
||||
context.pop();
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text("reset_sqlite_success".t(context: context))),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
"confirm".t(context: context),
|
||||
style: TextStyle(color: context.colorScheme.error),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 16, bottom: 32),
|
||||
child: ListView(
|
||||
children: [
|
||||
_SectionHeaderText(text: "assets".t(context: context)),
|
||||
const _SyncStatsCounts(),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
const SizedBox(height: 24),
|
||||
_SectionHeaderText(text: "jobs".t(context: context)),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"sync_local".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text("tap_to_run_job".t(context: context)),
|
||||
leading: const Icon(Icons.sync),
|
||||
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus),
|
||||
onTap: () {
|
||||
ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"sync_remote".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text("tap_to_run_job".t(context: context)),
|
||||
leading: const Icon(Icons.cloud_sync),
|
||||
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus),
|
||||
onTap: () {
|
||||
ref.read(backgroundSyncProvider).syncRemote();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"hash_asset".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: const Icon(Icons.tag),
|
||||
subtitle: Text("tap_to_run_job".t(context: context)),
|
||||
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus),
|
||||
onTap: () {
|
||||
ref.read(backgroundSyncProvider).hashAssets();
|
||||
},
|
||||
),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
const SizedBox(height: 24),
|
||||
_SectionHeaderText(text: "actions".t(context: context)),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"clear_file_cache".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: const Icon(Icons.playlist_remove_rounded),
|
||||
onTap: clearFileCache,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"export_database".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text("export_database_description".t(context: context)),
|
||||
leading: const Icon(Icons.download),
|
||||
onTap: exportDatabase,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"reset_sqlite".t(context: context),
|
||||
style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error),
|
||||
onTap: () async {
|
||||
await resetSqliteDb(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SyncStatusIcon extends StatelessWidget {
|
||||
final SyncStatus status;
|
||||
|
||||
const _SyncStatusIcon({required this.status});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (status) {
|
||||
SyncStatus.idle => const Icon(Icons.pause_circle_outline_rounded),
|
||||
SyncStatus.syncing => const SizedBox(height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
SyncStatus.success => const Icon(Icons.check_circle_outline, color: Colors.green),
|
||||
SyncStatus.error => Icon(Icons.error_outline, color: context.colorScheme.error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeaderText extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const _SectionHeaderText({required this.text});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Text(
|
||||
text.toUpperCase(),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: context.colorScheme.onSurface.withAlpha(200),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SyncStatsCounts extends ConsumerWidget {
|
||||
const _SyncStatsCounts();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assetService = ref.watch(assetServiceProvider);
|
||||
final localAlbumService = ref.watch(localAlbumServiceProvider);
|
||||
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
|
||||
final memoryService = ref.watch(driftMemoryServiceProvider);
|
||||
|
||||
Future<List<dynamic>> loadCounts() async {
|
||||
final assetCounts = assetService.getAssetCounts();
|
||||
final localAlbumCounts = localAlbumService.getCount();
|
||||
final remoteAlbumCounts = remoteAlbumService.getCount();
|
||||
final memoryCount = memoryService.getCount();
|
||||
final getLocalHashedCount = assetService.getLocalHashedCount();
|
||||
|
||||
return await Future.wait([assetCounts, localAlbumCounts, remoteAlbumCounts, memoryCount, getLocalHashedCount]);
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
future: loadCounts(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const Center(child: SizedBox(height: 48, width: 48, child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Error occur, reset the local database by tapping the button below",
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final assetCounts = snapshot.data![0]! as (int, int);
|
||||
final localAssetCount = assetCounts.$1;
|
||||
final remoteAssetCount = assetCounts.$2;
|
||||
|
||||
final localAlbumCount = snapshot.data![1]! as int;
|
||||
final remoteAlbumCount = snapshot.data![2]! as int;
|
||||
final memoryCount = snapshot.data![3]! as int;
|
||||
final localHashedCount = snapshot.data![4]! as int;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAssetCount,
|
||||
icon: Icons.smartphone,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAssetCount,
|
||||
icon: Icons.cloud,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_SectionHeaderText(text: "albums".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAlbumCount,
|
||||
icon: Icons.smartphone,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAlbumCount,
|
||||
icon: Icons.cloud,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_SectionHeaderText(text: "other".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "memories".t(context: context),
|
||||
count: memoryCount,
|
||||
icon: Icons.calendar_today,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "hashed_assets".t(context: context),
|
||||
count: localHashedCount,
|
||||
icon: Icons.tag,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
7
mobile/openapi/README.md
generated
7
mobile/openapi/README.md
generated
@@ -126,6 +126,7 @@ Class | Method | HTTP request | Description
|
||||
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up |
|
||||
*AuthenticationApi* | [**unlockAuthSession**](doc//AuthenticationApi.md#unlockauthsession) | **POST** /auth/session/unlock |
|
||||
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
||||
*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} |
|
||||
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random |
|
||||
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive |
|
||||
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info |
|
||||
@@ -171,7 +172,8 @@ Class | Method | HTTP request | Description
|
||||
*OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |
|
||||
*OAuthApi* | [**startOAuth**](doc//OAuthApi.md#startoauth) | **POST** /oauth/authorize |
|
||||
*OAuthApi* | [**unlinkOAuthAccount**](doc//OAuthApi.md#unlinkoauthaccount) | **POST** /oauth/unlink |
|
||||
*PartnersApi* | [**createPartner**](doc//PartnersApi.md#createpartner) | **POST** /partners/{id} |
|
||||
*PartnersApi* | [**createPartner**](doc//PartnersApi.md#createpartner) | **POST** /partners |
|
||||
*PartnersApi* | [**createPartnerDeprecated**](doc//PartnersApi.md#createpartnerdeprecated) | **POST** /partners/{id} |
|
||||
*PartnersApi* | [**getPartners**](doc//PartnersApi.md#getpartners) | **GET** /partners |
|
||||
*PartnersApi* | [**removePartner**](doc//PartnersApi.md#removepartner) | **DELETE** /partners/{id} |
|
||||
*PartnersApi* | [**updatePartner**](doc//PartnersApi.md#updatepartner) | **PUT** /partners/{id} |
|
||||
@@ -416,8 +418,10 @@ Class | Method | HTTP request | Description
|
||||
- [OnThisDayDto](doc//OnThisDayDto.md)
|
||||
- [OnboardingDto](doc//OnboardingDto.md)
|
||||
- [OnboardingResponseDto](doc//OnboardingResponseDto.md)
|
||||
- [PartnerCreateDto](doc//PartnerCreateDto.md)
|
||||
- [PartnerDirection](doc//PartnerDirection.md)
|
||||
- [PartnerResponseDto](doc//PartnerResponseDto.md)
|
||||
- [PartnerUpdateDto](doc//PartnerUpdateDto.md)
|
||||
- [PeopleResponse](doc//PeopleResponse.md)
|
||||
- [PeopleResponseDto](doc//PeopleResponseDto.md)
|
||||
- [PeopleUpdate](doc//PeopleUpdate.md)
|
||||
@@ -566,7 +570,6 @@ Class | Method | HTTP request | Description
|
||||
- [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md)
|
||||
- [UpdateAssetDto](doc//UpdateAssetDto.md)
|
||||
- [UpdateLibraryDto](doc//UpdateLibraryDto.md)
|
||||
- [UpdatePartnerDto](doc//UpdatePartnerDto.md)
|
||||
- [UsageByUserDto](doc//UsageByUserDto.md)
|
||||
- [UserAdminCreateDto](doc//UserAdminCreateDto.md)
|
||||
- [UserAdminDeleteDto](doc//UserAdminDeleteDto.md)
|
||||
|
||||
3
mobile/openapi/lib/api.dart
generated
3
mobile/openapi/lib/api.dart
generated
@@ -190,8 +190,10 @@ part 'model/o_auth_token_endpoint_auth_method.dart';
|
||||
part 'model/on_this_day_dto.dart';
|
||||
part 'model/onboarding_dto.dart';
|
||||
part 'model/onboarding_response_dto.dart';
|
||||
part 'model/partner_create_dto.dart';
|
||||
part 'model/partner_direction.dart';
|
||||
part 'model/partner_response_dto.dart';
|
||||
part 'model/partner_update_dto.dart';
|
||||
part 'model/people_response.dart';
|
||||
part 'model/people_response_dto.dart';
|
||||
part 'model/people_update.dart';
|
||||
@@ -340,7 +342,6 @@ part 'model/update_album_dto.dart';
|
||||
part 'model/update_album_user_dto.dart';
|
||||
part 'model/update_asset_dto.dart';
|
||||
part 'model/update_library_dto.dart';
|
||||
part 'model/update_partner_dto.dart';
|
||||
part 'model/usage_by_user_dto.dart';
|
||||
part 'model/user_admin_create_dto.dart';
|
||||
part 'model/user_admin_delete_dto.dart';
|
||||
|
||||
53
mobile/openapi/lib/api/deprecated_api.dart
generated
53
mobile/openapi/lib/api/deprecated_api.dart
generated
@@ -16,6 +16,59 @@ class DeprecatedApi {
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> createPartnerDeprecatedWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/partners/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<PartnerResponseDto?> createPartnerDeprecated(String id,) async {
|
||||
final response = await createPartnerDeprecatedWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
|
||||
72
mobile/openapi/lib/api/partners_api.dart
generated
72
mobile/openapi/lib/api/partners_api.dart
generated
@@ -22,8 +22,60 @@ class PartnersApi {
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [PartnerCreateDto] partnerCreateDto (required):
|
||||
Future<Response> createPartnerWithHttpInfo(PartnerCreateDto partnerCreateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/partners';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = partnerCreateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `partner.create` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [PartnerCreateDto] partnerCreateDto (required):
|
||||
Future<PartnerResponseDto?> createPartner(PartnerCreateDto partnerCreateDto,) async {
|
||||
final response = await createPartnerWithHttpInfo(partnerCreateDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> createPartnerWithHttpInfo(String id,) async {
|
||||
Future<Response> createPartnerDeprecatedWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/partners/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
@@ -49,13 +101,13 @@ class PartnersApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `partner.create` permission.
|
||||
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<PartnerResponseDto?> createPartner(String id,) async {
|
||||
final response = await createPartnerWithHttpInfo(id,);
|
||||
Future<PartnerResponseDto?> createPartnerDeprecated(String id,) async {
|
||||
final response = await createPartnerDeprecatedWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -179,14 +231,14 @@ class PartnersApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdatePartnerDto] updatePartnerDto (required):
|
||||
Future<Response> updatePartnerWithHttpInfo(String id, UpdatePartnerDto updatePartnerDto,) async {
|
||||
/// * [PartnerUpdateDto] partnerUpdateDto (required):
|
||||
Future<Response> updatePartnerWithHttpInfo(String id, PartnerUpdateDto partnerUpdateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/partners/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = updatePartnerDto;
|
||||
Object? postBody = partnerUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
@@ -212,9 +264,9 @@ class PartnersApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdatePartnerDto] updatePartnerDto (required):
|
||||
Future<PartnerResponseDto?> updatePartner(String id, UpdatePartnerDto updatePartnerDto,) async {
|
||||
final response = await updatePartnerWithHttpInfo(id, updatePartnerDto,);
|
||||
/// * [PartnerUpdateDto] partnerUpdateDto (required):
|
||||
Future<PartnerResponseDto?> updatePartner(String id, PartnerUpdateDto partnerUpdateDto,) async {
|
||||
final response = await updatePartnerWithHttpInfo(id, partnerUpdateDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
||||
30
mobile/openapi/lib/api/timeline_api.dart
generated
30
mobile/openapi/lib/api/timeline_api.dart
generated
@@ -53,12 +53,15 @@ class TimelineApi {
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)
|
||||
///
|
||||
/// * [bool] withCoordinates:
|
||||
/// Include location data in the response
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
/// Include assets shared by partners
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/timeline/bucket';
|
||||
|
||||
@@ -100,6 +103,9 @@ class TimelineApi {
|
||||
if (visibility != null) {
|
||||
queryParams.addAll(_queryParams('', 'visibility', visibility));
|
||||
}
|
||||
if (withCoordinates != null) {
|
||||
queryParams.addAll(_queryParams('', 'withCoordinates', withCoordinates));
|
||||
}
|
||||
if (withPartners != null) {
|
||||
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
|
||||
}
|
||||
@@ -156,13 +162,16 @@ class TimelineApi {
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)
|
||||
///
|
||||
/// * [bool] withCoordinates:
|
||||
/// Include location data in the response
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
/// Include assets shared by partners
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -210,12 +219,15 @@ class TimelineApi {
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)
|
||||
///
|
||||
/// * [bool] withCoordinates:
|
||||
/// Include location data in the response
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
/// Include assets shared by partners
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/timeline/buckets';
|
||||
|
||||
@@ -256,6 +268,9 @@ class TimelineApi {
|
||||
if (visibility != null) {
|
||||
queryParams.addAll(_queryParams('', 'visibility', visibility));
|
||||
}
|
||||
if (withCoordinates != null) {
|
||||
queryParams.addAll(_queryParams('', 'withCoordinates', withCoordinates));
|
||||
}
|
||||
if (withPartners != null) {
|
||||
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
|
||||
}
|
||||
@@ -309,13 +324,16 @@ class TimelineApi {
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)
|
||||
///
|
||||
/// * [bool] withCoordinates:
|
||||
/// Include location data in the response
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
/// Include assets shared by partners
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
||||
6
mobile/openapi/lib/api_client.dart
generated
6
mobile/openapi/lib/api_client.dart
generated
@@ -434,10 +434,14 @@ class ApiClient {
|
||||
return OnboardingDto.fromJson(value);
|
||||
case 'OnboardingResponseDto':
|
||||
return OnboardingResponseDto.fromJson(value);
|
||||
case 'PartnerCreateDto':
|
||||
return PartnerCreateDto.fromJson(value);
|
||||
case 'PartnerDirection':
|
||||
return PartnerDirectionTypeTransformer().decode(value);
|
||||
case 'PartnerResponseDto':
|
||||
return PartnerResponseDto.fromJson(value);
|
||||
case 'PartnerUpdateDto':
|
||||
return PartnerUpdateDto.fromJson(value);
|
||||
case 'PeopleResponse':
|
||||
return PeopleResponse.fromJson(value);
|
||||
case 'PeopleResponseDto':
|
||||
@@ -734,8 +738,6 @@ class ApiClient {
|
||||
return UpdateAssetDto.fromJson(value);
|
||||
case 'UpdateLibraryDto':
|
||||
return UpdateLibraryDto.fromJson(value);
|
||||
case 'UpdatePartnerDto':
|
||||
return UpdatePartnerDto.fromJson(value);
|
||||
case 'UsageByUserDto':
|
||||
return UsageByUserDto.fromJson(value);
|
||||
case 'UserAdminCreateDto':
|
||||
|
||||
99
mobile/openapi/lib/model/partner_create_dto.dart
generated
Normal file
99
mobile/openapi/lib/model/partner_create_dto.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class PartnerCreateDto {
|
||||
/// Returns a new [PartnerCreateDto] instance.
|
||||
PartnerCreateDto({
|
||||
required this.sharedWithId,
|
||||
});
|
||||
|
||||
String sharedWithId;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is PartnerCreateDto &&
|
||||
other.sharedWithId == sharedWithId;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(sharedWithId.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'PartnerCreateDto[sharedWithId=$sharedWithId]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'sharedWithId'] = this.sharedWithId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [PartnerCreateDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static PartnerCreateDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "PartnerCreateDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return PartnerCreateDto(
|
||||
sharedWithId: mapValueOfType<String>(json, r'sharedWithId')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<PartnerCreateDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <PartnerCreateDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = PartnerCreateDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, PartnerCreateDto> mapFromJson(dynamic json) {
|
||||
final map = <String, PartnerCreateDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = PartnerCreateDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of PartnerCreateDto-objects as value to a dart map
|
||||
static Map<String, List<PartnerCreateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<PartnerCreateDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = PartnerCreateDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'sharedWithId',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class UpdatePartnerDto {
|
||||
/// Returns a new [UpdatePartnerDto] instance.
|
||||
UpdatePartnerDto({
|
||||
class PartnerUpdateDto {
|
||||
/// Returns a new [PartnerUpdateDto] instance.
|
||||
PartnerUpdateDto({
|
||||
required this.inTimeline,
|
||||
});
|
||||
|
||||
bool inTimeline;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is UpdatePartnerDto &&
|
||||
bool operator ==(Object other) => identical(this, other) || other is PartnerUpdateDto &&
|
||||
other.inTimeline == inTimeline;
|
||||
|
||||
@override
|
||||
@@ -28,7 +28,7 @@ class UpdatePartnerDto {
|
||||
(inTimeline.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UpdatePartnerDto[inTimeline=$inTimeline]';
|
||||
String toString() => 'PartnerUpdateDto[inTimeline=$inTimeline]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -36,26 +36,26 @@ class UpdatePartnerDto {
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [UpdatePartnerDto] instance and imports its values from
|
||||
/// Returns a new [PartnerUpdateDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static UpdatePartnerDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "UpdatePartnerDto");
|
||||
static PartnerUpdateDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "PartnerUpdateDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return UpdatePartnerDto(
|
||||
return PartnerUpdateDto(
|
||||
inTimeline: mapValueOfType<bool>(json, r'inTimeline')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<UpdatePartnerDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <UpdatePartnerDto>[];
|
||||
static List<PartnerUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <PartnerUpdateDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = UpdatePartnerDto.fromJson(row);
|
||||
final value = PartnerUpdateDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
@@ -64,12 +64,12 @@ class UpdatePartnerDto {
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, UpdatePartnerDto> mapFromJson(dynamic json) {
|
||||
final map = <String, UpdatePartnerDto>{};
|
||||
static Map<String, PartnerUpdateDto> mapFromJson(dynamic json) {
|
||||
final map = <String, PartnerUpdateDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = UpdatePartnerDto.fromJson(entry.value);
|
||||
final value = PartnerUpdateDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
@@ -78,14 +78,14 @@ class UpdatePartnerDto {
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of UpdatePartnerDto-objects as value to a dart map
|
||||
static Map<String, List<UpdatePartnerDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<UpdatePartnerDto>>{};
|
||||
// maps a json object with a list of PartnerUpdateDto-objects as value to a dart map
|
||||
static Map<String, List<PartnerUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<PartnerUpdateDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = UpdatePartnerDto.listFromJson(entry.value, growable: growable,);
|
||||
map[entry.key] = PartnerUpdateDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
@@ -21,8 +21,10 @@ class TimeBucketAssetResponseDto {
|
||||
this.isFavorite = const [],
|
||||
this.isImage = const [],
|
||||
this.isTrashed = const [],
|
||||
this.latitude = const [],
|
||||
this.livePhotoVideoId = const [],
|
||||
this.localOffsetHours = const [],
|
||||
this.longitude = const [],
|
||||
this.ownerId = const [],
|
||||
this.projectionType = const [],
|
||||
this.ratio = const [],
|
||||
@@ -55,12 +57,18 @@ class TimeBucketAssetResponseDto {
|
||||
/// Array indicating whether each asset is in the trash
|
||||
List<bool> isTrashed;
|
||||
|
||||
/// Array of latitude coordinates extracted from EXIF GPS data
|
||||
List<num?> latitude;
|
||||
|
||||
/// Array of live photo video asset IDs (null for non-live photos)
|
||||
List<String?> livePhotoVideoId;
|
||||
|
||||
/// Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective.
|
||||
List<num> localOffsetHours;
|
||||
|
||||
/// Array of longitude coordinates extracted from EXIF GPS data
|
||||
List<num?> longitude;
|
||||
|
||||
/// Array of owner IDs for each asset
|
||||
List<String> ownerId;
|
||||
|
||||
@@ -89,8 +97,10 @@ class TimeBucketAssetResponseDto {
|
||||
_deepEquality.equals(other.isFavorite, isFavorite) &&
|
||||
_deepEquality.equals(other.isImage, isImage) &&
|
||||
_deepEquality.equals(other.isTrashed, isTrashed) &&
|
||||
_deepEquality.equals(other.latitude, latitude) &&
|
||||
_deepEquality.equals(other.livePhotoVideoId, livePhotoVideoId) &&
|
||||
_deepEquality.equals(other.localOffsetHours, localOffsetHours) &&
|
||||
_deepEquality.equals(other.longitude, longitude) &&
|
||||
_deepEquality.equals(other.ownerId, ownerId) &&
|
||||
_deepEquality.equals(other.projectionType, projectionType) &&
|
||||
_deepEquality.equals(other.ratio, ratio) &&
|
||||
@@ -109,8 +119,10 @@ class TimeBucketAssetResponseDto {
|
||||
(isFavorite.hashCode) +
|
||||
(isImage.hashCode) +
|
||||
(isTrashed.hashCode) +
|
||||
(latitude.hashCode) +
|
||||
(livePhotoVideoId.hashCode) +
|
||||
(localOffsetHours.hashCode) +
|
||||
(longitude.hashCode) +
|
||||
(ownerId.hashCode) +
|
||||
(projectionType.hashCode) +
|
||||
(ratio.hashCode) +
|
||||
@@ -119,7 +131,7 @@ class TimeBucketAssetResponseDto {
|
||||
(visibility.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'TimeBucketAssetResponseDto[city=$city, country=$country, duration=$duration, fileCreatedAt=$fileCreatedAt, id=$id, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, livePhotoVideoId=$livePhotoVideoId, localOffsetHours=$localOffsetHours, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash, visibility=$visibility]';
|
||||
String toString() => 'TimeBucketAssetResponseDto[city=$city, country=$country, duration=$duration, fileCreatedAt=$fileCreatedAt, id=$id, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, localOffsetHours=$localOffsetHours, longitude=$longitude, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -131,8 +143,10 @@ class TimeBucketAssetResponseDto {
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
json[r'isImage'] = this.isImage;
|
||||
json[r'isTrashed'] = this.isTrashed;
|
||||
json[r'latitude'] = this.latitude;
|
||||
json[r'livePhotoVideoId'] = this.livePhotoVideoId;
|
||||
json[r'localOffsetHours'] = this.localOffsetHours;
|
||||
json[r'longitude'] = this.longitude;
|
||||
json[r'ownerId'] = this.ownerId;
|
||||
json[r'projectionType'] = this.projectionType;
|
||||
json[r'ratio'] = this.ratio;
|
||||
@@ -175,12 +189,18 @@ class TimeBucketAssetResponseDto {
|
||||
isTrashed: json[r'isTrashed'] is Iterable
|
||||
? (json[r'isTrashed'] as Iterable).cast<bool>().toList(growable: false)
|
||||
: const [],
|
||||
latitude: json[r'latitude'] is Iterable
|
||||
? (json[r'latitude'] as Iterable).cast<num>().toList(growable: false)
|
||||
: const [],
|
||||
livePhotoVideoId: json[r'livePhotoVideoId'] is Iterable
|
||||
? (json[r'livePhotoVideoId'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
localOffsetHours: json[r'localOffsetHours'] is Iterable
|
||||
? (json[r'localOffsetHours'] as Iterable).cast<num>().toList(growable: false)
|
||||
: const [],
|
||||
longitude: json[r'longitude'] is Iterable
|
||||
? (json[r'longitude'] as Iterable).cast<num>().toList(growable: false)
|
||||
: const [],
|
||||
ownerId: json[r'ownerId'] is Iterable
|
||||
? (json[r'ownerId'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
|
||||
@@ -30,8 +30,9 @@ void main() {
|
||||
late SyncStreamService sut;
|
||||
late SyncStreamRepository mockSyncStreamRepo;
|
||||
late SyncApiRepository mockSyncApiRepo;
|
||||
late Function(List<SyncEvent>, Function()) handleEventsCallback;
|
||||
late Future<void> Function(List<SyncEvent>, Function(), Function()) handleEventsCallback;
|
||||
late _MockAbortCallbackWrapper mockAbortCallbackWrapper;
|
||||
late _MockAbortCallbackWrapper mockResetCallbackWrapper;
|
||||
|
||||
successHandler(Invocation _) async => true;
|
||||
|
||||
@@ -39,6 +40,7 @@ void main() {
|
||||
mockSyncStreamRepo = MockSyncStreamRepository();
|
||||
mockSyncApiRepo = MockSyncApiRepository();
|
||||
mockAbortCallbackWrapper = _MockAbortCallbackWrapper();
|
||||
mockResetCallbackWrapper = _MockAbortCallbackWrapper();
|
||||
|
||||
when(() => mockAbortCallbackWrapper()).thenReturn(false);
|
||||
|
||||
@@ -46,6 +48,10 @@ void main() {
|
||||
handleEventsCallback = invocation.positionalArguments.first;
|
||||
});
|
||||
|
||||
when(() => mockSyncApiRepo.streamChanges(any(), onReset: any(named: 'onReset'))).thenAnswer((invocation) async {
|
||||
handleEventsCallback = invocation.positionalArguments.first;
|
||||
});
|
||||
|
||||
when(() => mockSyncApiRepo.ack(any())).thenAnswer((_) async => {});
|
||||
|
||||
when(() => mockSyncStreamRepo.updateUsersV1(any())).thenAnswer(successHandler);
|
||||
@@ -86,7 +92,7 @@ void main() {
|
||||
|
||||
Future<void> simulateEvents(List<SyncEvent> events) async {
|
||||
await sut.sync();
|
||||
await handleEventsCallback(events, mockAbortCallbackWrapper.call);
|
||||
await handleEventsCallback(events, mockAbortCallbackWrapper.call, mockResetCallbackWrapper.call);
|
||||
}
|
||||
|
||||
group("SyncStreamService - _handleEvents", () {
|
||||
@@ -156,7 +162,7 @@ void main() {
|
||||
when(() => cancellationChecker()).thenReturn(true);
|
||||
});
|
||||
|
||||
await handleEventsCallback(events, mockAbortCallbackWrapper.call);
|
||||
await handleEventsCallback(events, mockAbortCallbackWrapper.call, mockResetCallbackWrapper.call);
|
||||
|
||||
verify(() => mockSyncStreamRepo.deleteUsersV1(any())).called(1);
|
||||
verifyNever(() => mockSyncStreamRepo.updateUsersV1(any()));
|
||||
@@ -188,7 +194,11 @@ void main() {
|
||||
|
||||
final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin, SyncStreamStub.partnerDeleteV1];
|
||||
|
||||
final processingFuture = handleEventsCallback(events, mockAbortCallbackWrapper.call);
|
||||
final processingFuture = handleEventsCallback(
|
||||
events,
|
||||
mockAbortCallbackWrapper.call,
|
||||
mockResetCallbackWrapper.call,
|
||||
);
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(handler1Started, isTrue);
|
||||
|
||||
5
mobile/test/drift/main/generated/schema.dart
generated
5
mobile/test/drift/main/generated/schema.dart
generated
@@ -12,6 +12,7 @@ import 'schema_v6.dart' as v6;
|
||||
import 'schema_v7.dart' as v7;
|
||||
import 'schema_v8.dart' as v8;
|
||||
import 'schema_v9.dart' as v9;
|
||||
import 'schema_v10.dart' as v10;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@@ -35,10 +36,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v8.DatabaseAtV8(db);
|
||||
case 9:
|
||||
return v9.DatabaseAtV9(db);
|
||||
case 10:
|
||||
return v10.DatabaseAtV10(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
}
|
||||
|
||||
7159
mobile/test/drift/main/generated/schema_v10.dart
generated
Normal file
7159
mobile/test/drift/main/generated/schema_v10.dart
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
mobile/test/fixtures/user.stub.dart
vendored
1
mobile/test/fixtures/user.stub.dart
vendored
@@ -1,5 +1,4 @@
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
|
||||
abstract final class UserStub {
|
||||
const UserStub._();
|
||||
|
||||
@@ -63,7 +63,9 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
Future<void> streamChanges(Function(List<SyncEvent>, Function() abort) onDataCallback) {
|
||||
Future<void> streamChanges(
|
||||
Future<void> Function(List<SyncEvent>, Function() abort, Function() reset) onDataCallback,
|
||||
) {
|
||||
return sut.streamChanges(onDataCallback, batchSize: testBatchSize, httpClient: mockHttpClient);
|
||||
}
|
||||
|
||||
@@ -72,7 +74,7 @@ void main() {
|
||||
bool abortWasCalledInCallback = false;
|
||||
List<SyncEvent> receivedEventsBatch1 = [];
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() abort) {
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() abort, Function() _) async {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
receivedEventsBatch1 = events;
|
||||
@@ -116,7 +118,7 @@ void main() {
|
||||
int onDataCallCount = 0;
|
||||
bool abortWasCalledInCallback = false;
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() abort) {
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() abort, Function() _) async {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
abort();
|
||||
@@ -158,7 +160,7 @@ void main() {
|
||||
List<SyncEvent> receivedEventsBatch1 = [];
|
||||
List<SyncEvent> receivedEventsBatch2 = [];
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() _) {
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() _, Function() __) async {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
receivedEventsBatch1 = events;
|
||||
@@ -202,7 +204,7 @@ void main() {
|
||||
final streamError = Exception("Network Error");
|
||||
int onDataCallCount = 0;
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() _) {
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() _, Function() __) async {
|
||||
onDataCallCount++;
|
||||
}
|
||||
|
||||
@@ -229,8 +231,7 @@ void main() {
|
||||
when(() => mockStreamedResponse.stream).thenAnswer((_) => http.ByteStream(errorBodyController.stream));
|
||||
|
||||
int onDataCallCount = 0;
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() _) {
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() _, Function() __) async {
|
||||
onDataCallCount++;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -110,6 +111,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -124,6 +126,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: true,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -141,6 +144,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -156,6 +160,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: true,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -171,6 +176,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -188,6 +194,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -203,6 +210,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -218,6 +226,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: true,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -233,6 +242,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -248,6 +258,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -265,6 +276,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -280,6 +292,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -295,6 +308,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -312,6 +326,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -327,6 +342,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -342,6 +358,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: true,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -359,6 +376,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -374,6 +392,7 @@ void main() {
|
||||
isTrashEnabled: false,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -391,6 +410,7 @@ void main() {
|
||||
isTrashEnabled: false,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -406,6 +426,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -423,6 +444,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -440,6 +462,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -457,6 +480,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -472,6 +496,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -489,6 +514,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -506,6 +532,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: album,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -520,6 +547,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -537,6 +565,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: album,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -552,6 +581,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: album,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -567,6 +597,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: album,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -581,12 +612,45 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
expect(ActionButtonType.likeActivity.shouldShow(context), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('advancedTroubleshooting button', () {
|
||||
test('should show when in advanced troubleshooting mode', () {
|
||||
final context = ActionButtonContext(
|
||||
asset: mergedAsset,
|
||||
isOwner: true,
|
||||
isArchived: false,
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: true,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
expect(ActionButtonType.advancedInfo.shouldShow(context), isTrue);
|
||||
});
|
||||
|
||||
test('should not show when not in advanced troubleshooting mode', () {
|
||||
final context = ActionButtonContext(
|
||||
asset: mergedAsset,
|
||||
isOwner: true,
|
||||
isArchived: false,
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
expect(ActionButtonType.advancedInfo.shouldShow(context), isFalse);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('ActionButtonType.buildButton', () {
|
||||
@@ -602,6 +666,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
});
|
||||
@@ -617,6 +682,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: album,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
final widget = buttonType.buildButton(contextWithAlbum);
|
||||
@@ -639,6 +705,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -658,6 +725,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: album,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -675,6 +743,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -693,6 +762,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
@@ -705,6 +775,7 @@ void main() {
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
|
||||
@@ -4994,6 +4994,48 @@
|
||||
],
|
||||
"x-immich-permission": "partner.read",
|
||||
"description": "This endpoint requires the `partner.read` permission."
|
||||
},
|
||||
"post": {
|
||||
"operationId": "createPartner",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PartnerCreateDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PartnerResponseDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Partners"
|
||||
],
|
||||
"x-immich-permission": "partner.create",
|
||||
"description": "This endpoint requires the `partner.create` permission."
|
||||
}
|
||||
},
|
||||
"/partners/{id}": {
|
||||
@@ -5033,7 +5075,9 @@
|
||||
"description": "This endpoint requires the `partner.delete` permission."
|
||||
},
|
||||
"post": {
|
||||
"operationId": "createPartner",
|
||||
"deprecated": true,
|
||||
"description": "This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.",
|
||||
"operationId": "createPartnerDeprecated",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
@@ -5069,10 +5113,13 @@
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Partners"
|
||||
"Partners",
|
||||
"Deprecated"
|
||||
],
|
||||
"x-immich-permission": "partner.create",
|
||||
"description": "This endpoint requires the `partner.create` permission."
|
||||
"x-immich-lifecycle": {
|
||||
"deprecatedAt": "v1.141.0"
|
||||
},
|
||||
"x-immich-permission": "partner.create"
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updatePartner",
|
||||
@@ -5091,7 +5138,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdatePartnerDto"
|
||||
"$ref": "#/components/schemas/PartnerUpdateDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8848,6 +8895,15 @@
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "withCoordinates",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Include location data in the response",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "withPartners",
|
||||
"required": false,
|
||||
@@ -8993,6 +9049,15 @@
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "withCoordinates",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Include location data in the response",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "withPartners",
|
||||
"required": false,
|
||||
@@ -12853,6 +12918,18 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PartnerCreateDto": {
|
||||
"properties": {
|
||||
"sharedWithId": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sharedWithId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PartnerDirection": {
|
||||
"enum": [
|
||||
"shared-by",
|
||||
@@ -12899,6 +12976,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PartnerUpdateDto": {
|
||||
"properties": {
|
||||
"inTimeline": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"inTimeline"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PeopleResponse": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
@@ -16996,6 +17084,14 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"latitude": {
|
||||
"description": "Array of latitude coordinates extracted from EXIF GPS data",
|
||||
"items": {
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"livePhotoVideoId": {
|
||||
"description": "Array of live photo video asset IDs (null for non-live photos)",
|
||||
"items": {
|
||||
@@ -17011,6 +17107,14 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"longitude": {
|
||||
"description": "Array of longitude coordinates extracted from EXIF GPS data",
|
||||
"items": {
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Array of owner IDs for each asset",
|
||||
"items": {
|
||||
@@ -17241,17 +17345,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UpdatePartnerDto": {
|
||||
"properties": {
|
||||
"inTimeline": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"inTimeline"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"UsageByUserDto": {
|
||||
"properties": {
|
||||
"photos": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user