Compare commits
1 Commits
feat/dav
...
flutter-3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adb59cf3a0 |
118
.github/actions/image-build/action.yml
vendored
Normal file
118
.github/actions/image-build/action.yml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: 'Single arch image build'
|
||||
description: 'Build single-arch image on platform appropriate runner'
|
||||
inputs:
|
||||
image:
|
||||
description: 'Name of the image to build'
|
||||
required: true
|
||||
ghcr-token:
|
||||
description: 'GitHub Container Registry token'
|
||||
required: true
|
||||
platform:
|
||||
description: 'Platform to build for'
|
||||
required: true
|
||||
artifact-key-base:
|
||||
description: 'Base key for artifact name'
|
||||
required: true
|
||||
context:
|
||||
description: 'Path to build context'
|
||||
required: true
|
||||
dockerfile:
|
||||
description: 'Path to Dockerfile'
|
||||
required: true
|
||||
build-args:
|
||||
description: 'Docker build arguments'
|
||||
required: false
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
shell: bash
|
||||
env:
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
run: |
|
||||
echo "platform-pair=${PLATFORM//\//-}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ inputs.ghcr-token }}
|
||||
|
||||
- name: Generate cache key suffix
|
||||
id: cache-key-suffix
|
||||
shell: bash
|
||||
env:
|
||||
REF: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
echo "cache-key-suffix=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
SUFFIX=$(echo "${REF}" | sed 's/[^a-zA-Z0-9]/-/g')
|
||||
echo "suffix=${SUFFIX}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Generate cache target
|
||||
id: cache-target
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_ARGS: ${{ inputs.build-args }}
|
||||
IMAGE: ${{ inputs.image }}
|
||||
SUFFIX: ${{ steps.cache-key-suffix.outputs.suffix }}
|
||||
PLATFORM_PAIR: ${{ steps.prepare.outputs.platform-pair }}
|
||||
run: |
|
||||
HASH=$(sha256sum <<< "${BUILD_ARGS}" | cut -d' ' -f1)
|
||||
CACHE_KEY="${PLATFORM_PAIR}-${HASH}"
|
||||
echo "cache-key-base=${CACHE_KEY}" >> $GITHUB_OUTPUT
|
||||
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
|
||||
# Essentially just ignore the cache output (forks can't write to registry cache)
|
||||
echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "cache-to=type=registry,ref=${IMAGE}-build-cache:${CACHE_KEY}-${SUFFIX},mode=max,compression=zstd" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Generate docker image tags
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
env:
|
||||
DOCKER_METADATA_PR_HEAD_SHA: 'true'
|
||||
|
||||
- name: Build and push image
|
||||
id: build
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
with:
|
||||
context: ${{ inputs.context }}
|
||||
file: ${{ inputs.dockerfile }}
|
||||
platforms: ${{ inputs.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-to: ${{ steps.cache-target.outputs.cache-to }}
|
||||
cache-from: |
|
||||
type=registry,ref=${{ inputs.image }}-build-cache:${{ steps.cache-target.outputs.cache-key-base }}-${{ steps.cache-key-suffix.outputs.suffix }}
|
||||
type=registry,ref=${{ inputs.image }}-build-cache:${{ steps.cache-target.outputs.cache-key-base }}-main
|
||||
outputs: type=image,"name=${{ inputs.image }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }}
|
||||
build-args: |
|
||||
BUILD_ID=${{ github.run_id }}
|
||||
BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.meta.outputs.tags }}
|
||||
BUILD_SOURCE_REF=${{ github.ref_name }}
|
||||
BUILD_SOURCE_COMMIT=${{ github.sha }}
|
||||
${{ inputs.build-args }}
|
||||
|
||||
- name: Export digest
|
||||
shell: bash
|
||||
run: | # zizmor: ignore[template-injection]
|
||||
mkdir -p ${{ runner.temp }}/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: ${{ inputs.artifact-key-base }}-${{ steps.cache-target.outputs.cache-key-base }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
185
.github/workflows/multi-runner-build.yml
vendored
Normal file
185
.github/workflows/multi-runner-build.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
name: 'Multi-runner container image build'
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image:
|
||||
description: 'Name of the image'
|
||||
type: string
|
||||
required: true
|
||||
context:
|
||||
description: 'Path to build context'
|
||||
type: string
|
||||
required: true
|
||||
dockerfile:
|
||||
description: 'Path to Dockerfile'
|
||||
type: string
|
||||
required: true
|
||||
tag-suffix:
|
||||
description: 'Suffix to append to the image tag'
|
||||
type: string
|
||||
default: ''
|
||||
dockerhub-push:
|
||||
description: 'Push to Docker Hub'
|
||||
type: boolean
|
||||
default: false
|
||||
build-args:
|
||||
description: 'Docker build arguments'
|
||||
type: string
|
||||
required: false
|
||||
platforms:
|
||||
description: 'Platforms to build for'
|
||||
type: string
|
||||
runner-mapping:
|
||||
description: 'Mapping from platforms to runners'
|
||||
type: string
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
env:
|
||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ inputs.image }}
|
||||
DOCKERHUB_IMAGE: altran1502/${{ inputs.image }}
|
||||
|
||||
jobs:
|
||||
matrix:
|
||||
name: 'Generate matrix'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.matrix.outputs.matrix }}
|
||||
key: ${{ steps.artifact-key.outputs.base }}
|
||||
steps:
|
||||
- name: Generate build matrix
|
||||
id: matrix
|
||||
shell: bash
|
||||
env:
|
||||
PLATFORMS: ${{ inputs.platforms || 'linux/amd64,linux/arm64' }}
|
||||
RUNNER_MAPPING: ${{ inputs.runner-mapping || '{"linux/amd64":"ubuntu-latest","linux/arm64":"ubuntu-24.04-arm"}' }}
|
||||
run: |
|
||||
matrix=$(jq -R -c \
|
||||
--argjson runner_mapping "${RUNNER_MAPPING}" \
|
||||
'split(",") | map({platform: ., runner: $runner_mapping[.]})' \
|
||||
<<< "${PLATFORMS}")
|
||||
echo "${matrix}"
|
||||
echo "matrix=${matrix}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Determine artifact key
|
||||
id: artifact-key
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ inputs.image }}
|
||||
SUFFIX: ${{ inputs.tag-suffix }}
|
||||
run: |
|
||||
if [[ -n "${SUFFIX}" ]]; then
|
||||
base="${IMAGE}${SUFFIX}-digests"
|
||||
else
|
||||
base="${IMAGE}-digests"
|
||||
fi
|
||||
echo "${base}"
|
||||
echo "base=${base}" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: matrix
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.matrix.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: ./.github/actions/image-build
|
||||
with:
|
||||
context: ${{ inputs.context }}
|
||||
dockerfile: ${{ inputs.dockerfile }}
|
||||
image: ${{ env.GHCR_IMAGE }}
|
||||
ghcr-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
platform: ${{ matrix.platform }}
|
||||
artifact-key-base: ${{ needs.matrix.outputs.key }}
|
||||
build-args: ${{ inputs.build-args }}
|
||||
|
||||
merge:
|
||||
needs: [matrix, build]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: ${{ needs.matrix.outputs.key }}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ inputs.dockerhub-push }}
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- name: Generate docker image tags
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
env:
|
||||
DOCKER_METADATA_PR_HEAD_SHA: 'true'
|
||||
with:
|
||||
flavor: |
|
||||
# Disable latest tag
|
||||
latest=false
|
||||
suffix=${{ inputs.tag-suffix }}
|
||||
images: |
|
||||
name=${{ env.GHCR_IMAGE }}
|
||||
name=${{ env.DOCKERHUB_IMAGE }},enable=${{ inputs.dockerhub-push }}
|
||||
tags: |
|
||||
# Tag with branch name
|
||||
type=ref,event=branch
|
||||
# Tag with pr-number
|
||||
type=ref,event=pr
|
||||
# Tag with long commit sha hash
|
||||
type=sha,format=long,prefix=commit-
|
||||
# Tag with git tag on release
|
||||
type=ref,event=tag
|
||||
type=raw,value=release,enable=${{ github.event_name == 'release' }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
run: |
|
||||
# Process annotations
|
||||
declare -a ANNOTATIONS=()
|
||||
if [[ -n "$DOCKER_METADATA_OUTPUT_JSON" ]]; then
|
||||
while IFS= read -r annotation; do
|
||||
# Extract key and value by removing the manifest: prefix
|
||||
if [[ "$annotation" =~ ^manifest:(.+)=(.+)$ ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[2]}"
|
||||
# Use array to properly handle arguments with spaces
|
||||
ANNOTATIONS+=(--annotation "index:$key=$value")
|
||||
fi
|
||||
done < <(jq -r '.annotations[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
|
||||
fi
|
||||
|
||||
TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
SOURCE_ARGS=$(printf "${GHCR_IMAGE}@sha256:%s " *)
|
||||
|
||||
docker buildx imagetools create $TAGS "${ANNOTATIONS[@]}" $SOURCE_ARGS
|
||||
@@ -43,7 +43,6 @@ export interface UploadOptionsDto {
|
||||
concurrency: number;
|
||||
progress?: boolean;
|
||||
watch?: boolean;
|
||||
jsonOutput?: boolean;
|
||||
}
|
||||
|
||||
class UploadFile extends File {
|
||||
@@ -66,9 +65,6 @@ class UploadFile extends File {
|
||||
const uploadBatch = async (files: string[], options: UploadOptionsDto) => {
|
||||
const { newFiles, duplicates } = await checkForDuplicates(files, options);
|
||||
const newAssets = await uploadFiles(newFiles, options);
|
||||
if (options.jsonOutput) {
|
||||
console.log(JSON.stringify({ newFiles, duplicates, newAssets }, undefined, 4));
|
||||
}
|
||||
await updateAlbums([...newAssets, ...duplicates], options);
|
||||
await deleteFiles(newFiles, options);
|
||||
};
|
||||
|
||||
@@ -68,11 +68,6 @@ program
|
||||
.env('IMMICH_UPLOAD_CONCURRENCY')
|
||||
.default(4),
|
||||
)
|
||||
.addOption(
|
||||
new Option('-j, --json-output', 'Output detailed information in json format')
|
||||
.env('IMMICH_JSON_OUTPUT')
|
||||
.default(false),
|
||||
)
|
||||
.addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS'))
|
||||
.addOption(new Option('--no-progress', 'Hide progress bars').env('IMMICH_PROGRESS_BAR').default(true))
|
||||
.addOption(
|
||||
|
||||
@@ -116,7 +116,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:a19bebed6a91bd5e6e2106fef015f9602a3392deeb7c9ed47548378dcee3dfc2
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:ff21bc0f8194dc9c105b769aeabf9585fea6a8ed649c0781caeac5cb3c247884
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:a19bebed6a91bd5e6e2106fef015f9602a3392deeb7c9ed47548378dcee3dfc2
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:ff21bc0f8194dc9c105b769aeabf9585fea6a8ed649c0781caeac5cb3c247884
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
@@ -49,7 +49,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:a19bebed6a91bd5e6e2106fef015f9602a3392deeb7c9ed47548378dcee3dfc2
|
||||
image: docker.io/valkey/valkey:8-bookworm@sha256:ff21bc0f8194dc9c105b769aeabf9585fea6a8ed649c0781caeac5cb3c247884
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
@@ -219,10 +219,3 @@ When you turn off the storage template engine, it will leave the assets in `UPLO
|
||||
Do not touch the files inside these folders under any circumstances except taking a backup. Changing or removing an asset can cause untracked and missing files.
|
||||
You can think of it as App-Which-Must-Not-Be-Named, the only access to viewing, changing and deleting assets is only through the mobile or browser interface.
|
||||
:::
|
||||
|
||||
## Backup ordering
|
||||
|
||||
A backup of Immich should contain both the database and the asset files. When backing these up it's possible for them to get out of sync, potentially resulting in broken assets after you restore.
|
||||
The best way of dealing with this is to stop the immich-server container while you take a backup. If nothing is changing then the backup will always be in sync.
|
||||
|
||||
If stopping the container is not an option, then the recommended order is to back up the database first, and the filesystem second. This way, the worst case scenario is that there are files on the filesystem that the database doesn't know about. If necessary, these can be (re)uploaded manually after a restore. If the backup is done the other way around, with the filesystem first and the database second, it's possible for the restored database to reference files that aren't in the filesystem backup, thus resulting in broken assets.
|
||||
|
||||
@@ -93,7 +93,6 @@ The `.well-known/openid-configuration` part of the url is optional and will be a
|
||||
## Auto Launch
|
||||
|
||||
When Auto Launch is enabled, the login page will automatically redirect the user to the OAuth authorization url, to login with OAuth. To access the login screen again, use the browser's back button, or navigate directly to `/auth/login?autoLaunch=0`.
|
||||
Auto Launch can also be enabled on a per-request basis by navigating to `/auth/login?authLaunch=1`, this can be useful in situations where Immich is called from e.g. Nextcloud using the _External sites_ app and the _oidc_ app so as to enable users to directly interact with a logged-in instance of Immich.
|
||||
|
||||
## Mobile Redirect URI
|
||||
|
||||
|
||||
@@ -90,22 +90,19 @@ Usage: immich upload [paths...] [options]
|
||||
Upload assets
|
||||
|
||||
Arguments:
|
||||
paths One or more paths to assets to be uploaded
|
||||
paths One or more paths to assets to be uploaded
|
||||
|
||||
Options:
|
||||
-r, --recursive Recursive (default: false, env: IMMICH_RECURSIVE)
|
||||
-i, --ignore <pattern> Pattern to ignore (env: IMMICH_IGNORE_PATHS)
|
||||
-h, --skip-hash Don't hash files before upload (default: false, env: IMMICH_SKIP_HASH)
|
||||
-H, --include-hidden Include hidden folders (default: false, env: IMMICH_INCLUDE_HIDDEN)
|
||||
-a, --album Automatically create albums based on folder name (default: false, env: IMMICH_AUTO_CREATE_ALBUM)
|
||||
-A, --album-name <name> Add all assets to specified album (env: IMMICH_ALBUM_NAME)
|
||||
-n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN)
|
||||
-c, --concurrency <number> Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY)
|
||||
-j, --json-output Output detailed information in json format (default: false, env: IMMICH_JSON_OUTPUT)
|
||||
--delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS)
|
||||
--no-progress Hide progress bars (env: IMMICH_PROGRESS_BAR)
|
||||
--watch Watch for changes and upload automatically (default: false, env: IMMICH_WATCH_CHANGES)
|
||||
--help display help for command
|
||||
-r, --recursive Recursive (default: false, env: IMMICH_RECURSIVE)
|
||||
-i, --ignore [paths...] Paths to ignore (default: [], env: IMMICH_IGNORE_PATHS)
|
||||
-h, --skip-hash Don't hash files before upload (default: false, env: IMMICH_SKIP_HASH)
|
||||
-H, --include-hidden Include hidden folders (default: false, env: IMMICH_INCLUDE_HIDDEN)
|
||||
-a, --album Automatically create albums based on folder name (default: false, env: IMMICH_AUTO_CREATE_ALBUM)
|
||||
-A, --album-name <name> Add all assets to specified album (env: IMMICH_ALBUM_NAME)
|
||||
-n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN)
|
||||
-c, --concurrency <number> Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY)
|
||||
--delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS)
|
||||
--help display help for command
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -175,16 +172,6 @@ By default, hidden files are skipped. If you want to include hidden files, use t
|
||||
immich upload --include-hidden --recursive directory/
|
||||
```
|
||||
|
||||
You can use the `--json-output` option to get a json printed which includes
|
||||
three keys: `newFiles`, `duplicates` and `newAssets`. Due to some logging
|
||||
output you will need to strip the first three lines of output to get the json.
|
||||
For example to get a list of files that would be uploaded for further
|
||||
processing:
|
||||
|
||||
```bash
|
||||
immich upload --dry-run . | tail -n +4 | jq .newFiles[]
|
||||
```
|
||||
|
||||
### Obtain the API Key
|
||||
|
||||
The API key can be obtained in the user setting panel on the web interface.
|
||||
|
||||
@@ -13,9 +13,6 @@ import {
|
||||
mdiTrashCan,
|
||||
mdiWeb,
|
||||
mdiWrap,
|
||||
mdiCloudKeyOutline,
|
||||
mdiRegex,
|
||||
mdiCodeJson,
|
||||
} from '@mdi/js';
|
||||
import Layout from '@theme/Layout';
|
||||
import React from 'react';
|
||||
@@ -26,30 +23,6 @@ const withLanguage = (date: Date) => (language: string) => date.toLocaleDateStri
|
||||
type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date };
|
||||
|
||||
const items: Item[] = [
|
||||
{
|
||||
icon: mdiRegex,
|
||||
iconColor: 'purple',
|
||||
title: 'Zitadel Actions are cursed',
|
||||
description:
|
||||
"Zitadel is cursed because its custom scripting feature is executed with a JS engine that doesn't support regex named capture groups.",
|
||||
link: {
|
||||
url: 'https://github.com/dop251/goja',
|
||||
text: 'Go JS engine',
|
||||
},
|
||||
date: new Date(2025, 5, 4),
|
||||
},
|
||||
{
|
||||
icon: mdiCloudKeyOutline,
|
||||
iconColor: '#0078d4',
|
||||
title: 'Entra is cursed',
|
||||
description:
|
||||
"Microsoft Entra supports PKCE, but doesn't include it in its OpenID discovery document. This leads to clients thinking PKCE isn't available.",
|
||||
link: {
|
||||
url: 'https://github.com/immich-app/immich/pull/18725',
|
||||
text: '#18725',
|
||||
},
|
||||
date: new Date(2025, 4, 30),
|
||||
},
|
||||
{
|
||||
icon: mdiCrop,
|
||||
iconColor: 'tomato',
|
||||
@@ -60,18 +33,7 @@ const items: Item[] = [
|
||||
url: 'https://github.com/immich-app/immich/pull/17974',
|
||||
text: '#17974',
|
||||
},
|
||||
date: new Date(2025, 4, 5),
|
||||
},
|
||||
{
|
||||
icon: mdiCodeJson,
|
||||
iconColor: 'yellow',
|
||||
title: 'YAML whitespace is cursed',
|
||||
description: 'YAML whitespaces are often handled in unintuitive ways.',
|
||||
link: {
|
||||
url: 'https://github.com/immich-app/immich/pull/17309',
|
||||
text: '#17309',
|
||||
},
|
||||
date: new Date(2025, 3, 1),
|
||||
date: new Date(2025, 5, 5),
|
||||
},
|
||||
{
|
||||
icon: mdiMicrosoftWindows,
|
||||
|
||||
@@ -28,10 +28,8 @@ services:
|
||||
extra_hosts:
|
||||
- 'auth-server:host-gateway'
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_started
|
||||
database:
|
||||
condition: service_healthy
|
||||
- redis
|
||||
- database
|
||||
ports:
|
||||
- 2285:2285
|
||||
|
||||
@@ -39,7 +37,7 @@ services:
|
||||
image: redis:6.2-alpine@sha256:3211c33a618c457e5d241922c975dbc4f446d0bdb2dc75694f5573ef8e2d01fa
|
||||
|
||||
database:
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:9c704fb49ce27549df00f1b096cc93f8b0c959ef087507704d74954808f78a82
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:e6d1209c1c13791c6f9fbf726c41865e3320dfe2445a6b4ffb03e25f904b3b37
|
||||
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
@@ -47,9 +45,3 @@ services:
|
||||
POSTGRES_DB: immich
|
||||
ports:
|
||||
- 5435:5432
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres -d immich']
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
|
||||
@@ -428,15 +428,6 @@ describe('/albums', () => {
|
||||
order: AssetOrder.Desc,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to share album with owner', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/albums')
|
||||
.send({ albumName: 'New album', albumUsers: [{ role: AlbumUserRole.Editor, userId: user1.userId }] })
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('Cannot share album with owner'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /albums/:id/assets', () => {
|
||||
|
||||
@@ -75,8 +75,8 @@ describe('/timeline', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ count: 3, timeBucket: '1970-02-01' },
|
||||
{ count: 1, timeBucket: '1970-01-01' },
|
||||
{ count: 3, timeBucket: '1970-02-01T00:00:00.000Z' },
|
||||
{ count: 1, timeBucket: '1970-01-01T00:00:00.000Z' },
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -167,8 +167,7 @@ describe('/timeline', () => {
|
||||
isImage: [],
|
||||
isTrashed: [],
|
||||
livePhotoVideoId: [],
|
||||
fileCreatedAt: [],
|
||||
localOffsetHours: [],
|
||||
localDateTime: [],
|
||||
ownerId: [],
|
||||
projectionType: [],
|
||||
ratio: [],
|
||||
@@ -205,8 +204,7 @@ describe('/timeline', () => {
|
||||
isImage: [],
|
||||
isTrashed: [],
|
||||
livePhotoVideoId: [],
|
||||
fileCreatedAt: [],
|
||||
localOffsetHours: [],
|
||||
localDateTime: [],
|
||||
ownerId: [],
|
||||
projectionType: [],
|
||||
ratio: [],
|
||||
|
||||
@@ -103,7 +103,6 @@ export const loginResponseDto = {
|
||||
accessToken: expect.any(String),
|
||||
name: 'Immich Admin',
|
||||
isAdmin: true,
|
||||
isOnboarded: false,
|
||||
profileImagePath: '',
|
||||
shouldChangePassword: true,
|
||||
userEmail: 'admin@immich.cloud',
|
||||
|
||||
@@ -33,9 +33,7 @@ test.describe('Registration', () => {
|
||||
// onboarding
|
||||
await expect(page).toHaveURL('/auth/onboarding');
|
||||
await page.getByRole('button', { name: 'Theme' }).click();
|
||||
await page.getByRole('button', { name: 'Language' }).click();
|
||||
await page.getByRole('button', { name: 'Server Privacy' }).click();
|
||||
await page.getByRole('button', { name: 'User Privacy' }).click();
|
||||
await page.getByRole('button', { name: 'Privacy' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Template' }).click();
|
||||
await page.getByRole('button', { name: 'Done' }).click();
|
||||
|
||||
@@ -79,13 +77,6 @@ test.describe('Registration', () => {
|
||||
await page.getByLabel('Password').fill('new-password');
|
||||
await page.getByRole('button', { name: 'Login' }).click();
|
||||
|
||||
// onboarding
|
||||
await expect(page).toHaveURL('/auth/onboarding');
|
||||
await page.getByRole('button', { name: 'Theme' }).click();
|
||||
await page.getByRole('button', { name: 'Language' }).click();
|
||||
await page.getByRole('button', { name: 'User Privacy' }).click();
|
||||
await page.getByRole('button', { name: 'Done' }).click();
|
||||
|
||||
// success
|
||||
await expect(page).toHaveURL(/\/photos/);
|
||||
});
|
||||
|
||||
80
i18n/en.json
80
i18n/en.json
@@ -26,6 +26,7 @@
|
||||
"add_to_album": "Add to album",
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"add_to_locked_folder": "Add to locked folder",
|
||||
"add_to_shared_album": "Add to shared album",
|
||||
"add_url": "Add URL",
|
||||
"added_to_archive": "Added to archive",
|
||||
@@ -43,7 +44,9 @@
|
||||
"backup_database_enable_description": "Enable database dumps",
|
||||
"backup_keep_last_amount": "Amount of previous dumps to keep",
|
||||
"backup_settings": "Database Dump Settings",
|
||||
"backup_settings_description": "Manage database dump settings.",
|
||||
"backup_settings_description": "Manage database dump settings. Note: These jobs are not monitored and you will not be notified of failure.",
|
||||
"check_all": "Check All",
|
||||
"cleanup": "Cleanup",
|
||||
"cleared_jobs": "Cleared jobs for: {job}",
|
||||
"config_set_by_file": "Config is currently set by a config file",
|
||||
"confirm_delete_library": "Are you sure you want to delete {library} library?",
|
||||
@@ -59,12 +62,14 @@
|
||||
"disable_login": "Disable login",
|
||||
"duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search",
|
||||
"exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.",
|
||||
"external_library_created_at": "External library (created on {date})",
|
||||
"external_library_management": "External Library Management",
|
||||
"face_detection": "Face detection",
|
||||
"face_detection_description": "Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. \"Refresh\" (re-)processes all assets. \"Reset\" additionally clears all current face data. \"Missing\" queues assets that haven't been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.",
|
||||
"facial_recognition_job_description": "Group detected faces into people. This step runs after Face Detection is complete. \"Reset\" (re-)clusters all faces. \"Missing\" queues faces that don't have a person assigned.",
|
||||
"failed_job_command": "Command {command} failed for job: {job}",
|
||||
"force_delete_user_warning": "WARNING: This will immediately remove the user and all assets. This cannot be undone and the files cannot be recovered.",
|
||||
"forcing_refresh_library_files": "Forcing refresh of all library files",
|
||||
"image_format": "Format",
|
||||
"image_format_description": "WebP produces smaller files than JPEG, but is slower to encode.",
|
||||
"image_fullsize_description": "Full-size image with stripped metadata, used when zoomed in",
|
||||
@@ -205,6 +210,8 @@
|
||||
"oauth_storage_quota_default_description": "Quota in GiB to be used when no claim is provided (Enter 0 for unlimited quota).",
|
||||
"oauth_timeout": "Request Timeout",
|
||||
"oauth_timeout_description": "Timeout for requests in milliseconds",
|
||||
"offline_paths": "Offline Paths",
|
||||
"offline_paths_description": "These results may be due to manual deletion of files that are not part of an external library.",
|
||||
"password_enable_description": "Login with email and password",
|
||||
"password_settings": "Password Login",
|
||||
"password_settings_description": "Manage password login settings",
|
||||
@@ -214,6 +221,9 @@
|
||||
"refreshing_all_libraries": "Refreshing all libraries",
|
||||
"registration": "Admin Registration",
|
||||
"registration_description": "Since you are the first user on the system, you will be assigned as the Admin and are responsible for administrative tasks, and additional users will be created by you.",
|
||||
"repair_all": "Repair All",
|
||||
"repair_matched_items": "Matched {count, plural, one {# item} other {# items}}",
|
||||
"repaired_items": "Repaired {count, plural, one {# item} other {# items}}",
|
||||
"require_password_change_on_login": "Require user to change password on first login",
|
||||
"reset_settings_to_default": "Reset settings to default",
|
||||
"reset_settings_to_recent_saved": "Reset settings to the recent saved settings",
|
||||
@@ -254,6 +264,7 @@
|
||||
"template_email_invite_album": "Invite Album Template",
|
||||
"template_email_preview": "Preview",
|
||||
"template_email_settings": "Email Templates",
|
||||
"template_email_settings_description": "Manage custom email notification templates",
|
||||
"template_email_update_album": "Update Album Template",
|
||||
"template_email_welcome": "Welcome email template",
|
||||
"template_settings": "Notification Templates",
|
||||
@@ -262,6 +273,7 @@
|
||||
"theme_custom_css_settings_description": "Cascading Style Sheets allow the design of Immich to be customized.",
|
||||
"theme_settings": "Theme Settings",
|
||||
"theme_settings_description": "Manage customization of the Immich web interface",
|
||||
"these_files_matched_by_checksum": "These files are matched by their checksums",
|
||||
"thumbnail_generation_job": "Generate Thumbnails",
|
||||
"thumbnail_generation_job_description": "Generate large, small and blurred thumbnails for each asset, as well as thumbnails for each person",
|
||||
"transcoding_acceleration_api": "Acceleration API",
|
||||
@@ -329,6 +341,8 @@
|
||||
"trash_number_of_days_description": "Number of days to keep the assets in trash before permanently removing them",
|
||||
"trash_settings": "Trash Settings",
|
||||
"trash_settings_description": "Manage trash settings",
|
||||
"untracked_files": "Untracked Files",
|
||||
"untracked_files_description": "These files are not tracked by the application. They can be the results of failed moves, interrupted uploads, or left behind due to a bug",
|
||||
"user_cleanup_job": "User cleanup",
|
||||
"user_delete_delay": "<b>{user}</b>'s account and assets will be scheduled for permanent deletion in {delay, plural, one {# day} other {# days}}.",
|
||||
"user_delete_delay_settings": "Delete delay",
|
||||
@@ -387,6 +401,10 @@
|
||||
"album_remove_user": "Remove user?",
|
||||
"album_remove_user_confirmation": "Are you sure you want to remove {user}?",
|
||||
"album_share_no_users": "Looks like you have shared this album with all users or you don't have any user to share with.",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
"album_thumbnail_card_items": "{count} items",
|
||||
"album_thumbnail_card_shared": " · Shared",
|
||||
"album_thumbnail_shared_by": "Shared by {user}",
|
||||
"album_updated": "Album updated",
|
||||
"album_updated_setting_description": "Receive an email notification when a shared album has new assets",
|
||||
"album_user_left": "Left {album}",
|
||||
@@ -402,9 +420,6 @@
|
||||
"album_with_link_access": "Let anyone with the link see photos and people in this album.",
|
||||
"albums": "Albums",
|
||||
"albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albums}}",
|
||||
"albums_default_sort_order": "Default album sort order",
|
||||
"albums_default_sort_order_description": "Initial asset sort order when creating new albums.",
|
||||
"albums_feature_description": "Collections of assets that can be shared with other users.",
|
||||
"all": "All",
|
||||
"all_albums": "All albums",
|
||||
"all_people": "All people",
|
||||
@@ -466,8 +481,6 @@
|
||||
"assets_count": "{count, plural, one {# asset} other {# assets}}",
|
||||
"assets_deleted_permanently": "{count} asset(s) deleted permanently",
|
||||
"assets_deleted_permanently_from_server": "{count} asset(s) deleted permanently from the Immich server",
|
||||
"assets_downloaded_failed": "{count, plural, one {Downloaded # file - {error} file failed} other {Downloaded # files - {error} files failed}}",
|
||||
"assets_downloaded_successfully": "{count, plural, one {Downloaded # file successfully} other {Downloaded # files successfully}}",
|
||||
"assets_moved_to_trash_count": "Moved {count, plural, one {# asset} other {# assets}} to trash",
|
||||
"assets_permanently_deleted_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}",
|
||||
"assets_removed_count": "Removed {count, plural, one {# asset} other {# assets}}",
|
||||
@@ -482,7 +495,6 @@
|
||||
"authorized_devices": "Authorized Devices",
|
||||
"automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere",
|
||||
"automatic_endpoint_switching_title": "Automatic URL switching",
|
||||
"autoplay_slideshow": "Autoplay slideshow",
|
||||
"back": "Back",
|
||||
"back_close_deselect": "Back, close, or deselect",
|
||||
"background_location_permission": "Background location permission",
|
||||
@@ -564,17 +576,21 @@
|
||||
"bulk_keep_duplicates_confirmation": "Are you sure you want to keep {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will resolve all duplicate groups without deleting anything.",
|
||||
"bulk_trash_duplicates_confirmation": "Are you sure you want to bulk trash {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and trash all other duplicates.",
|
||||
"buy": "Purchase Immich",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({count} assets)",
|
||||
"cache_settings_clear_cache_button": "Clear cache",
|
||||
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||
"cache_settings_duplicated_assets_clear_button": "CLEAR",
|
||||
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
|
||||
"cache_settings_duplicated_assets_title": "Duplicated Assets ({count})",
|
||||
"cache_settings_image_cache_size": "Image cache size ({count} assets)",
|
||||
"cache_settings_statistics_album": "Library thumbnails",
|
||||
"cache_settings_statistics_assets": "{count} assets ({size})",
|
||||
"cache_settings_statistics_full": "Full images",
|
||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||
"cache_settings_statistics_thumbnail": "Thumbnails",
|
||||
"cache_settings_statistics_title": "Cache usage",
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({count} assets)",
|
||||
"cache_settings_tile_subtitle": "Control the local storage behaviour",
|
||||
"cache_settings_tile_title": "Local Storage",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
@@ -606,6 +622,7 @@
|
||||
"change_pin_code": "Change PIN code",
|
||||
"change_your_password": "Change your password",
|
||||
"changed_visibility_successfully": "Changed visibility successfully",
|
||||
"check_all": "Check All",
|
||||
"check_corrupt_asset_backup": "Check for corrupt asset backups",
|
||||
"check_corrupt_asset_backup_button": "Perform check",
|
||||
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
|
||||
@@ -651,6 +668,7 @@
|
||||
"contain": "Contain",
|
||||
"context": "Context",
|
||||
"continue": "Continue",
|
||||
"control_bottom_app_bar_album_info_shared": "{count} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
||||
@@ -699,7 +717,6 @@
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"dark": "Dark",
|
||||
"darkTheme": "Toggle dark theme",
|
||||
"date_after": "Date after",
|
||||
"date_and_time": "Date and Time",
|
||||
"date_before": "Date before",
|
||||
@@ -762,6 +779,7 @@
|
||||
"download_enqueue": "Download enqueued",
|
||||
"download_error": "Download Error",
|
||||
"download_failed": "Download failed",
|
||||
"download_filename": "file: {filename}",
|
||||
"download_finished": "Download finished",
|
||||
"download_include_embedded_motion_videos": "Embedded videos",
|
||||
"download_include_embedded_motion_videos_description": "Include videos embedded in motion photos as a separate file",
|
||||
@@ -837,6 +855,7 @@
|
||||
"cant_get_number_of_comments": "Can't get number of comments",
|
||||
"cant_search_people": "Can't search people",
|
||||
"cant_search_places": "Can't search places",
|
||||
"cleared_jobs": "Cleared jobs for: {job}",
|
||||
"error_adding_assets_to_album": "Error adding assets to album",
|
||||
"error_adding_users_to_album": "Error adding users to album",
|
||||
"error_deleting_shared_user": "Error deleting shared user",
|
||||
@@ -845,6 +864,7 @@
|
||||
"error_removing_assets_from_album": "Error removing assets from album, check console for more details",
|
||||
"error_selecting_all_assets": "Error selecting all assets",
|
||||
"exclusion_pattern_already_exists": "This exclusion pattern already exists.",
|
||||
"failed_job_command": "Command {command} failed for job: {job}",
|
||||
"failed_to_create_album": "Failed to create album",
|
||||
"failed_to_create_shared_link": "Failed to create shared link",
|
||||
"failed_to_edit_shared_link": "Failed to edit shared link",
|
||||
@@ -863,6 +883,7 @@
|
||||
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
|
||||
"profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.",
|
||||
"quota_higher_than_disk_size": "You set a quota higher than the disk size",
|
||||
"repair_unable_to_check_items": "Unable to check {count, select, one {item} other {items}}",
|
||||
"unable_to_add_album_users": "Unable to add users to album",
|
||||
"unable_to_add_assets_to_shared_link": "Unable to add assets to shared link",
|
||||
"unable_to_add_comment": "Unable to add comment",
|
||||
@@ -881,6 +902,7 @@
|
||||
"unable_to_change_visibility": "Unable to change the visibility for {count, plural, one {# person} other {# people}}",
|
||||
"unable_to_complete_oauth_login": "Unable to complete OAuth login",
|
||||
"unable_to_connect": "Unable to connect",
|
||||
"unable_to_connect_to_server": "Unable to connect to server",
|
||||
"unable_to_copy_to_clipboard": "Cannot copy to clipboard, make sure you are accessing the page through https",
|
||||
"unable_to_create_admin_account": "Unable to create admin account",
|
||||
"unable_to_create_api_key": "Unable to create a new API Key",
|
||||
@@ -904,9 +926,14 @@
|
||||
"unable_to_hide_person": "Unable to hide person",
|
||||
"unable_to_link_motion_video": "Unable to link motion video",
|
||||
"unable_to_link_oauth_account": "Unable to link OAuth account",
|
||||
"unable_to_load_album": "Unable to load album",
|
||||
"unable_to_load_asset_activity": "Unable to load asset activity",
|
||||
"unable_to_load_items": "Unable to load items",
|
||||
"unable_to_load_liked_status": "Unable to load liked status",
|
||||
"unable_to_log_out_all_devices": "Unable to log out all devices",
|
||||
"unable_to_log_out_device": "Unable to log out device",
|
||||
"unable_to_login_with_oauth": "Unable to login with OAuth",
|
||||
"unable_to_move_to_locked_folder": "Unable to move to locked folder",
|
||||
"unable_to_play_video": "Unable to play video",
|
||||
"unable_to_reassign_assets_existing_person": "Unable to reassign assets to {name, select, null {an existing person} other {{name}}}",
|
||||
"unable_to_reassign_assets_new_person": "Unable to reassign assets to a new person",
|
||||
@@ -914,9 +941,11 @@
|
||||
"unable_to_remove_album_users": "Unable to remove users from album",
|
||||
"unable_to_remove_api_key": "Unable to remove API Key",
|
||||
"unable_to_remove_assets_from_shared_link": "Unable to remove assets from shared link",
|
||||
"unable_to_remove_deleted_assets": "Unable to remove offline files",
|
||||
"unable_to_remove_library": "Unable to remove library",
|
||||
"unable_to_remove_partner": "Unable to remove partner",
|
||||
"unable_to_remove_reaction": "Unable to remove reaction",
|
||||
"unable_to_repair_items": "Unable to repair items",
|
||||
"unable_to_reset_password": "Unable to reset password",
|
||||
"unable_to_reset_pin_code": "Unable to reset PIN code",
|
||||
"unable_to_resolve_duplicate": "Unable to resolve duplicate",
|
||||
@@ -1089,12 +1118,6 @@
|
||||
"invalid_date_format": "Invalid date format",
|
||||
"invite_people": "Invite People",
|
||||
"invite_to_album": "Invite to album",
|
||||
"ios_debug_info_fetch_ran_at": "Fetch ran {dateTime}",
|
||||
"ios_debug_info_last_sync_at": "Last sync {dateTime}",
|
||||
"ios_debug_info_no_processes_queued": "No background processes queued",
|
||||
"ios_debug_info_no_sync_yet": "No background sync job has run yet",
|
||||
"ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}",
|
||||
"ios_debug_info_processing_ran_at": "Processing ran {dateTime}",
|
||||
"items_count": "{count, plural, one {# item} other {# items}}",
|
||||
"jobs": "Jobs",
|
||||
"keep": "Keep",
|
||||
@@ -1103,9 +1126,6 @@
|
||||
"kept_this_deleted_others": "Kept this asset and deleted {count, plural, one {# asset} other {# assets}}",
|
||||
"keyboard_shortcuts": "Keyboard shortcuts",
|
||||
"language": "Language",
|
||||
"language_no_results_subtitle": "Try adjusting your search term",
|
||||
"language_no_results_title": "No languages found",
|
||||
"language_search_hint": "Search languages...",
|
||||
"language_setting_description": "Select your preferred language",
|
||||
"last_seen": "Last seen",
|
||||
"latest_version": "Latest Version",
|
||||
@@ -1141,7 +1161,7 @@
|
||||
"location_picker_longitude_error": "Enter a valid longitude",
|
||||
"location_picker_longitude_hint": "Enter your longitude here",
|
||||
"lock": "Lock",
|
||||
"locked_folder": "Locked Folder",
|
||||
"locked_folder": "Locked folder",
|
||||
"log_out": "Log out",
|
||||
"log_out_all_devices": "Log Out All Devices",
|
||||
"logged_out_all_devices": "Logged out all devices",
|
||||
@@ -1175,7 +1195,7 @@
|
||||
"look": "Look",
|
||||
"loop_videos": "Loop videos",
|
||||
"loop_videos_description": "Enable to automatically loop a video in the detail viewer.",
|
||||
"main_branch_warning": "You're using a development version; we strongly recommend using a release version!",
|
||||
"main_branch_warning": "You’re using a development version; we strongly recommend using a release version!",
|
||||
"main_menu": "Main menu",
|
||||
"make": "Make",
|
||||
"manage_shared_links": "Manage shared links",
|
||||
@@ -1299,15 +1319,15 @@
|
||||
"oauth": "OAuth",
|
||||
"official_immich_resources": "Official Immich Resources",
|
||||
"offline": "Offline",
|
||||
"offline_paths": "Offline paths",
|
||||
"offline_paths_description": "These results may be due to manual deletion of files that are not part of an external library.",
|
||||
"ok": "Ok",
|
||||
"oldest_first": "Oldest first",
|
||||
"on_this_device": "On this device",
|
||||
"onboarding": "Onboarding",
|
||||
"onboarding_locale_description": "Select your preferred language. You can change this later in your settings.",
|
||||
"onboarding_privacy_description": "The following (optional) features rely on external services, and can be disabled at any time in settings.",
|
||||
"onboarding_server_welcome_description": "Let's get your instance set up with some common settings.",
|
||||
"onboarding_privacy_description": "The following (optional) features rely on external services, and can be disabled at any time in the administration settings.",
|
||||
"onboarding_theme_description": "Choose a color theme for your instance. You can change this later in your settings.",
|
||||
"onboarding_user_welcome_description": "Let's get you started!",
|
||||
"onboarding_welcome_description": "Let's get your instance set up with some common settings.",
|
||||
"onboarding_welcome_user": "Welcome, {user}",
|
||||
"online": "Online",
|
||||
"only_favorites": "Only favorites",
|
||||
@@ -1440,7 +1460,7 @@
|
||||
"purchase_lifetime_description": "Lifetime purchase",
|
||||
"purchase_option_title": "PURCHASE OPTIONS",
|
||||
"purchase_panel_info_1": "Building Immich takes a lot of time and effort, and we have full-time engineers working on it to make it as good as we possibly can. Our mission is for open-source software and ethical business practices to become a sustainable income source for developers and to create a privacy-respecting ecosystem with real alternatives to exploitative cloud services.",
|
||||
"purchase_panel_info_2": "As we're committed not to add paywalls, this purchase will not grant you any additional features in Immich. We rely on users like you to support Immich's ongoing development.",
|
||||
"purchase_panel_info_2": "As we’re committed not to add paywalls, this purchase will not grant you any additional features in Immich. We rely on users like you to support Immich’s ongoing development.",
|
||||
"purchase_panel_title": "Support the project",
|
||||
"purchase_per_server": "Per server",
|
||||
"purchase_per_user": "Per user",
|
||||
@@ -1619,7 +1639,6 @@
|
||||
"server_info_box_server_url": "Server URL",
|
||||
"server_offline": "Server Offline",
|
||||
"server_online": "Server Online",
|
||||
"server_privacy": "Server Privacy",
|
||||
"server_stats": "Server Stats",
|
||||
"server_version": "Server Version",
|
||||
"set": "Set",
|
||||
@@ -1637,6 +1656,7 @@
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_subtitle": "Change the app's language",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {duration}",
|
||||
"setting_notifications_notify_hours": "{count} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
@@ -1823,6 +1843,7 @@
|
||||
"to_parent": "Go to parent",
|
||||
"to_trash": "Trash",
|
||||
"toggle_settings": "Toggle settings",
|
||||
"toggle_theme": "Toggle dark theme",
|
||||
"total": "Total",
|
||||
"total_usage": "Total usage",
|
||||
"trash": "Trash",
|
||||
@@ -1844,7 +1865,6 @@
|
||||
"unable_to_setup_pin_code": "Unable to setup PIN code",
|
||||
"unarchive": "Unarchive",
|
||||
"unarchived_count": "{count, plural, other {Unarchived #}}",
|
||||
"undo": "Undo",
|
||||
"unfavorite": "Unfavorite",
|
||||
"unhide_person": "Unhide person",
|
||||
"unknown": "Unknown",
|
||||
@@ -1863,6 +1883,8 @@
|
||||
"unselect_all_duplicates": "Unselect all duplicates",
|
||||
"unstack": "Un-stack",
|
||||
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
|
||||
"untracked_files": "Untracked files",
|
||||
"untracked_files_decription": "These files are not tracked by the application. They can be the results of failed moves, interrupted uploads, or left behind due to a bug",
|
||||
"up_next": "Up next",
|
||||
"updated_at": "Updated",
|
||||
"updated_password": "Updated password",
|
||||
@@ -1890,7 +1912,6 @@
|
||||
"user_liked": "{user} liked {type, select, photo {this photo} video {this video} asset {this asset} other {it}}",
|
||||
"user_pin_code_settings": "PIN Code",
|
||||
"user_pin_code_settings_description": "Manage your PIN code",
|
||||
"user_privacy": "User Privacy",
|
||||
"user_purchase_settings": "Purchase",
|
||||
"user_purchase_settings_description": "Manage your purchase",
|
||||
"user_role_set": "Set {user} as {role}",
|
||||
@@ -1906,6 +1927,11 @@
|
||||
"version": "Version",
|
||||
"version_announcement_closing": "Your friend, Alex",
|
||||
"version_announcement_message": "Hi there! A new version of Immich is available. Please take some time to read the <link>release notes</link> to ensure your setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your Immich instance automatically.",
|
||||
"version_announcement_overlay_release_notes": "release notes",
|
||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||
"version_announcement_overlay_title": "New Server Version Available 🎉",
|
||||
"version_history": "Version History",
|
||||
"version_history_item": "Installed {version} on {date}",
|
||||
"video": "Video",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"flutter": "3.29.3"
|
||||
}
|
||||
"flutter": "3.32.0"
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ custom_lint:
|
||||
allowed:
|
||||
# required / wanted
|
||||
- 'lib/infrastructure/repositories/album_media.repository.dart'
|
||||
- 'lib/infrastructure/repositories/storage.repository.dart'
|
||||
- 'lib/repositories/{album,asset,file}_media.repository.dart'
|
||||
# acceptable exceptions for the time being
|
||||
- lib/entities/asset.entity.dart # to provide local AssetEntity for now
|
||||
|
||||
@@ -247,7 +247,6 @@ interface NativeSyncApi {
|
||||
fun getAlbums(): List<PlatformAlbum>
|
||||
fun getAssetsCountSince(albumId: String, timestamp: Long): Long
|
||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||
fun hashPaths(paths: List<String>): List<ByteArray?>
|
||||
|
||||
companion object {
|
||||
/** The codec used by NativeSyncApi. */
|
||||
@@ -389,23 +388,6 @@ interface NativeSyncApi {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashPaths$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val pathsArg = args[0] as List<String>
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.hashPaths(pathsArg))
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
sealed class AssetResult {
|
||||
data class ValidAsset(val asset: PlatformAsset, val albumId: String) : AssetResult()
|
||||
@@ -19,8 +16,6 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||
private val ctx: Context = context.applicationContext
|
||||
|
||||
companion object {
|
||||
private const val TAG = "NativeSyncApiImplBase"
|
||||
|
||||
const val MEDIA_SELECTION =
|
||||
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||
val MEDIA_SELECTION_ARGS = arrayOf(
|
||||
@@ -39,8 +34,6 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||
MediaStore.MediaColumns.BUCKET_ID,
|
||||
MediaStore.MediaColumns.DURATION
|
||||
)
|
||||
|
||||
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
|
||||
}
|
||||
|
||||
protected fun getCursor(
|
||||
@@ -181,24 +174,4 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||
.mapNotNull { result -> (result as? AssetResult.ValidAsset)?.asset }
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun hashPaths(paths: List<String>): List<ByteArray?> {
|
||||
val buffer = ByteArray(HASH_BUFFER_SIZE)
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
|
||||
return paths.map { path ->
|
||||
try {
|
||||
FileInputStream(path).use { file ->
|
||||
var bytesRead: Int
|
||||
while (file.read(buffer).also { bytesRead = it } > 0) {
|
||||
digest.update(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
digest.digest()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to hash file $path: $e")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
mobile/drift_schemas/main/drift_schema_v1.json
generated
2
mobile/drift_schemas/main/drift_schema_v1.json
generated
File diff suppressed because one or more lines are too long
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -43,6 +44,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
disableMainThreadChecker = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
|
||||
@@ -307,7 +307,6 @@ protocol NativeSyncApi {
|
||||
func getAlbums() throws -> [PlatformAlbum]
|
||||
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64
|
||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||
func hashPaths(paths: [String]) throws -> [FlutterStandardTypedData?]
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
@@ -443,22 +442,5 @@ class NativeSyncApiSetup {
|
||||
} else {
|
||||
getAssetsForAlbumChannel.setMessageHandler(nil)
|
||||
}
|
||||
let hashPathsChannel = taskQueue == nil
|
||||
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashPaths\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashPaths\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||
if let api = api {
|
||||
hashPathsChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let pathsArg = args[0] as! [String]
|
||||
do {
|
||||
let result = try api.hashPaths(paths: pathsArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hashPathsChannel.setMessageHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Photos
|
||||
import CryptoKit
|
||||
|
||||
struct AssetWrapper: Hashable, Equatable {
|
||||
let asset: PlatformAsset
|
||||
@@ -35,8 +34,6 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||
private let changeTokenKey = "immich:changeToken"
|
||||
private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum]
|
||||
|
||||
private let hashBufferSize = 2 * 1024 * 1024
|
||||
|
||||
init(with defaults: UserDefaults = .standard) {
|
||||
self.defaults = defaults
|
||||
}
|
||||
@@ -246,24 +243,4 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
func hashPaths(paths: [String]) throws -> [FlutterStandardTypedData?] {
|
||||
return paths.map { path in
|
||||
guard let file = FileHandle(forReadingAtPath: path) else {
|
||||
print("Cannot open file: \(path)")
|
||||
return nil
|
||||
}
|
||||
|
||||
var hasher = Insecure.SHA1()
|
||||
while autoreleasepool(invoking: {
|
||||
let chunk = file.readData(ofLength: hashBufferSize)
|
||||
guard !chunk.isEmpty else { return false }
|
||||
hasher.update(data: chunk)
|
||||
return true
|
||||
}) { }
|
||||
|
||||
let digest = hasher.finalize()
|
||||
return FlutterStandardTypedData(bytes: Data(digest))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ const Map<String, Locale> locales = {
|
||||
'English (en)': Locale('en'),
|
||||
// Additional locales
|
||||
'Arabic (ar)': Locale('ar'),
|
||||
'Bulgarian (bg)': Locale('bg'),
|
||||
'Catalan (ca)': Locale('ca'),
|
||||
'Chinese Simplified (zh_CN)':
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'SIMPLIFIED'),
|
||||
@@ -32,7 +31,6 @@ const Map<String, Locale> locales = {
|
||||
'Mongolian (mn)': Locale('mn'),
|
||||
'Norwegian Bokmål (nb_NO)': Locale('nb', 'NO'),
|
||||
'Polish (pl)': Locale('pl'),
|
||||
'Brazilian Portuguese (pt_BR)': Locale('pt', 'BR'),
|
||||
'Portuguese (pt)': Locale('pt'),
|
||||
'Romanian (ro)': Locale('ro'),
|
||||
'Russian (ru)': Locale('ru'),
|
||||
@@ -44,8 +42,6 @@ const Map<String, Locale> locales = {
|
||||
'Slovenian (sl)': Locale('sl'),
|
||||
'Spanish (es)': Locale('es'),
|
||||
'Swedish (sv)': Locale('sv'),
|
||||
'Tamil (ta)': Locale('ta'),
|
||||
'Telugu (te)': Locale('te'),
|
||||
'Thai (th)': Locale('th'),
|
||||
'Turkish (tr)': Locale('tr'),
|
||||
'Ukrainian (uk)': Locale('uk'),
|
||||
|
||||
@@ -29,8 +29,6 @@ abstract interface class ILocalAlbumRepository implements IDatabaseRepository {
|
||||
String albumId,
|
||||
Iterable<String> assetIdsToKeep,
|
||||
);
|
||||
|
||||
Future<List<LocalAsset>> getAssetsToHash(String albumId);
|
||||
}
|
||||
|
||||
enum SortLocalAlbumsBy { id }
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
|
||||
abstract interface class ILocalAssetRepository implements IDatabaseRepository {
|
||||
Future<void> updateHashes(Iterable<LocalAsset> hashes);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
|
||||
abstract interface class IStorageRepository {
|
||||
Future<File?> getFileForAsset(LocalAsset asset);
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
part of 'base_asset.model.dart';
|
||||
|
||||
enum AssetVisibility {
|
||||
timeline,
|
||||
hidden,
|
||||
archive,
|
||||
locked,
|
||||
}
|
||||
|
||||
// Model for an asset stored in the server
|
||||
class Asset extends BaseAsset {
|
||||
final String id;
|
||||
final String? localId;
|
||||
final AssetVisibility visibility;
|
||||
|
||||
const Asset({
|
||||
required this.id,
|
||||
@@ -25,7 +17,6 @@ class Asset extends BaseAsset {
|
||||
super.height,
|
||||
super.durationInSeconds,
|
||||
super.isFavorite = false,
|
||||
this.visibility = AssetVisibility.timeline,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -41,7 +32,6 @@ class Asset extends BaseAsset {
|
||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||
localId: ${localId ?? "<NA>"},
|
||||
isFavorite: $isFavorite,
|
||||
visibility: $visibility,
|
||||
}''';
|
||||
}
|
||||
|
||||
@@ -49,13 +39,9 @@ class Asset extends BaseAsset {
|
||||
bool operator ==(Object other) {
|
||||
if (other is! Asset) return false;
|
||||
if (identical(this, other)) return true;
|
||||
return super == other &&
|
||||
id == other.id &&
|
||||
localId == other.localId &&
|
||||
visibility == other.visibility;
|
||||
return super == other && id == other.id && localId == other.localId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
super.hashCode ^ id.hashCode ^ localId.hashCode ^ visibility.hashCode;
|
||||
int get hashCode => super.hashCode ^ id.hashCode ^ localId.hashCode;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
enum BackupSelection {
|
||||
none._(1),
|
||||
selected._(0),
|
||||
excluded._(2);
|
||||
|
||||
// Used to sort albums based on the backupSelection
|
||||
// selected -> none -> excluded
|
||||
final int sortOrder;
|
||||
const BackupSelection._(this.sortOrder);
|
||||
none,
|
||||
selected,
|
||||
excluded,
|
||||
}
|
||||
|
||||
class LocalAlbum {
|
||||
final String id;
|
||||
final String name;
|
||||
final DateTime updatedAt;
|
||||
final bool isIosSharedAlbum;
|
||||
|
||||
final int assetCount;
|
||||
final BackupSelection backupSelection;
|
||||
@@ -24,7 +18,6 @@ class LocalAlbum {
|
||||
required this.updatedAt,
|
||||
this.assetCount = 0,
|
||||
this.backupSelection = BackupSelection.none,
|
||||
this.isIosSharedAlbum = false,
|
||||
});
|
||||
|
||||
LocalAlbum copyWith({
|
||||
@@ -33,7 +26,6 @@ class LocalAlbum {
|
||||
DateTime? updatedAt,
|
||||
int? assetCount,
|
||||
BackupSelection? backupSelection,
|
||||
bool? isIosSharedAlbum,
|
||||
}) {
|
||||
return LocalAlbum(
|
||||
id: id ?? this.id,
|
||||
@@ -41,7 +33,6 @@ class LocalAlbum {
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
assetCount: assetCount ?? this.assetCount,
|
||||
backupSelection: backupSelection ?? this.backupSelection,
|
||||
isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,8 +45,7 @@ class LocalAlbum {
|
||||
other.name == name &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.assetCount == assetCount &&
|
||||
other.backupSelection == backupSelection &&
|
||||
other.isIosSharedAlbum == isIosSharedAlbum;
|
||||
other.backupSelection == backupSelection;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -64,8 +54,7 @@ class LocalAlbum {
|
||||
name.hashCode ^
|
||||
updatedAt.hashCode ^
|
||||
assetCount.hashCode ^
|
||||
backupSelection.hashCode ^
|
||||
isIosSharedAlbum.hashCode;
|
||||
backupSelection.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -76,7 +65,6 @@ name: $name,
|
||||
updatedAt: $updatedAt,
|
||||
assetCount: $assetCount,
|
||||
backupSelection: $backupSelection,
|
||||
isIosSharedAlbum: $isIosSharedAlbum
|
||||
}''';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/local_album.interface.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/storage.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class HashService {
|
||||
final int batchSizeLimit;
|
||||
final int batchFileLimit;
|
||||
final ILocalAlbumRepository _localAlbumRepository;
|
||||
final ILocalAssetRepository _localAssetRepository;
|
||||
final IStorageRepository _storageRepository;
|
||||
final NativeSyncApi _nativeSyncApi;
|
||||
final _log = Logger('HashService');
|
||||
|
||||
HashService({
|
||||
required ILocalAlbumRepository localAlbumRepository,
|
||||
required ILocalAssetRepository localAssetRepository,
|
||||
required IStorageRepository storageRepository,
|
||||
required NativeSyncApi nativeSyncApi,
|
||||
this.batchSizeLimit = kBatchHashSizeLimit,
|
||||
this.batchFileLimit = kBatchHashFileLimit,
|
||||
}) : _localAlbumRepository = localAlbumRepository,
|
||||
_localAssetRepository = localAssetRepository,
|
||||
_storageRepository = storageRepository,
|
||||
_nativeSyncApi = nativeSyncApi;
|
||||
|
||||
Future<void> hashAssets() async {
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
// Sorted by backupSelection followed by isCloud
|
||||
final localAlbums = await _localAlbumRepository.getAll();
|
||||
localAlbums.sort((a, b) {
|
||||
final backupComparison =
|
||||
a.backupSelection.sortOrder.compareTo(b.backupSelection.sortOrder);
|
||||
|
||||
if (backupComparison != 0) {
|
||||
return backupComparison;
|
||||
}
|
||||
|
||||
// Local albums come before iCloud albums
|
||||
return (a.isIosSharedAlbum ? 1 : 0).compareTo(b.isIosSharedAlbum ? 1 : 0);
|
||||
});
|
||||
|
||||
for (final album in localAlbums) {
|
||||
final assetsToHash =
|
||||
await _localAlbumRepository.getAssetsToHash(album.id);
|
||||
if (assetsToHash.isNotEmpty) {
|
||||
await _hashAssets(assetsToHash);
|
||||
}
|
||||
}
|
||||
|
||||
stopwatch.stop();
|
||||
_log.info("Hashing took - ${stopwatch.elapsedMilliseconds}ms");
|
||||
DLog.log("Hashing took - ${stopwatch.elapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
/// Processes a list of [LocalAsset]s, storing their hash and updating the assets in the DB
|
||||
/// with hash for those that were successfully hashed. Hashes are looked up in a table
|
||||
/// [LocalAssetHashEntity] by local id. Only missing entries are newly hashed and added to the DB.
|
||||
Future<void> _hashAssets(List<LocalAsset> assetsToHash) async {
|
||||
int bytesProcessed = 0;
|
||||
final toHash = <_AssetToPath>[];
|
||||
|
||||
for (final asset in assetsToHash) {
|
||||
final file = await _storageRepository.getFileForAsset(asset);
|
||||
if (file == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bytesProcessed += await file.length();
|
||||
toHash.add(_AssetToPath(asset: asset, path: file.path));
|
||||
|
||||
if (toHash.length >= batchFileLimit || bytesProcessed >= batchSizeLimit) {
|
||||
await _processBatch(toHash);
|
||||
toHash.clear();
|
||||
bytesProcessed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
await _processBatch(toHash);
|
||||
}
|
||||
|
||||
/// Processes a batch of assets.
|
||||
Future<void> _processBatch(List<_AssetToPath> toHash) async {
|
||||
if (toHash.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_log.fine("Hashing ${toHash.length} files");
|
||||
|
||||
final hashed = <LocalAsset>[];
|
||||
final hashes =
|
||||
await _nativeSyncApi.hashPaths(toHash.map((e) => e.path).toList());
|
||||
|
||||
for (final (index, hash) in hashes.indexed) {
|
||||
final asset = toHash[index].asset;
|
||||
if (hash?.length == 20) {
|
||||
hashed.add(asset.copyWith(checksum: base64.encode(hash!)));
|
||||
} else {
|
||||
_log.warning("Failed to hash file ${asset.id}");
|
||||
}
|
||||
}
|
||||
|
||||
_log.fine("Hashed ${hashed.length}/${toHash.length} assets");
|
||||
DLog.log("Hashed ${hashed.length}/${toHash.length} assets");
|
||||
|
||||
await _localAssetRepository.updateHashes(hashed);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetToPath {
|
||||
final LocalAsset asset;
|
||||
final String path;
|
||||
|
||||
const _AssetToPath({required this.asset, required this.path});
|
||||
}
|
||||
@@ -365,7 +365,6 @@ extension on Iterable<PlatformAsset> {
|
||||
(e) => LocalAsset(
|
||||
id: e.id,
|
||||
name: e.name,
|
||||
checksum: null,
|
||||
type: AssetType.values.elementAtOrNull(e.type) ?? AssetType.other,
|
||||
createdAt: e.createdAt == null
|
||||
? DateTime.now()
|
||||
|
||||
@@ -63,6 +63,7 @@ class SyncStreamService {
|
||||
Iterable<dynamic> data,
|
||||
) async {
|
||||
_logger.fine("Processing sync data for $type of length ${data.length}");
|
||||
// ignore: prefer-switch-expression
|
||||
switch (type) {
|
||||
case SyncEntityType.userV1:
|
||||
return _syncStreamRepository.updateUsersV1(data.cast());
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:worker_manager/worker_manager.dart';
|
||||
class BackgroundSyncManager {
|
||||
Cancelable<void>? _syncTask;
|
||||
Cancelable<void>? _deviceAlbumSyncTask;
|
||||
Cancelable<void>? _hashTask;
|
||||
|
||||
BackgroundSyncManager();
|
||||
|
||||
@@ -46,20 +45,6 @@ class BackgroundSyncManager {
|
||||
});
|
||||
}
|
||||
|
||||
// No need to cancel the task, as it can also be run when the user logs out
|
||||
Future<void> hashAssets() {
|
||||
if (_hashTask != null) {
|
||||
return _hashTask!.future;
|
||||
}
|
||||
|
||||
_hashTask = runInIsolateGentle(
|
||||
computation: (ref) => ref.read(hashServiceProvider).hashAssets(),
|
||||
);
|
||||
return _hashTask!.whenComplete(() {
|
||||
_hashTask = null;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> syncRemote() {
|
||||
if (_syncTask != null) {
|
||||
return _syncTask!.future;
|
||||
|
||||
@@ -19,7 +19,6 @@ class Album {
|
||||
required this.name,
|
||||
required this.createdAt,
|
||||
required this.modifiedAt,
|
||||
this.description,
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.lastModifiedAssetTimestamp,
|
||||
@@ -35,7 +34,6 @@ class Album {
|
||||
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||
String? localId;
|
||||
String name;
|
||||
String? description;
|
||||
DateTime createdAt;
|
||||
DateTime modifiedAt;
|
||||
DateTime? startDate;
|
||||
@@ -110,7 +108,6 @@ class Album {
|
||||
remoteId == other.remoteId &&
|
||||
localId == other.localId &&
|
||||
name == other.name &&
|
||||
description == other.description &&
|
||||
createdAt.isAtSameMomentAs(other.createdAt) &&
|
||||
modifiedAt.isAtSameMomentAs(other.modifiedAt) &&
|
||||
isAtSameMomentAs(startDate, other.startDate) &&
|
||||
@@ -138,7 +135,6 @@ class Album {
|
||||
modifiedAt.hashCode ^
|
||||
startDate.hashCode ^
|
||||
endDate.hashCode ^
|
||||
description.hashCode ^
|
||||
lastModifiedAssetTimestamp.hashCode ^
|
||||
shared.hashCode ^
|
||||
activityEnabled.hashCode ^
|
||||
@@ -154,7 +150,6 @@ class Album {
|
||||
name: dto.albumName,
|
||||
createdAt: dto.createdAt,
|
||||
modifiedAt: dto.updatedAt,
|
||||
description: dto.description,
|
||||
lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp,
|
||||
shared: dto.shared,
|
||||
startDate: dto.startDate,
|
||||
@@ -189,8 +184,7 @@ class Album {
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'remoteId: $remoteId name: $name description: $description';
|
||||
String toString() => name;
|
||||
}
|
||||
|
||||
extension AssetsHelper on IsarCollection<Album> {
|
||||
|
||||
269
mobile/lib/entities/album.entity.g.dart
generated
269
mobile/lib/entities/album.entity.g.dart
generated
@@ -27,54 +27,49 @@ const AlbumSchema = CollectionSchema(
|
||||
name: r'createdAt',
|
||||
type: IsarType.dateTime,
|
||||
),
|
||||
r'description': PropertySchema(
|
||||
id: 2,
|
||||
name: r'description',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'endDate': PropertySchema(
|
||||
id: 3,
|
||||
id: 2,
|
||||
name: r'endDate',
|
||||
type: IsarType.dateTime,
|
||||
),
|
||||
r'lastModifiedAssetTimestamp': PropertySchema(
|
||||
id: 4,
|
||||
id: 3,
|
||||
name: r'lastModifiedAssetTimestamp',
|
||||
type: IsarType.dateTime,
|
||||
),
|
||||
r'localId': PropertySchema(
|
||||
id: 5,
|
||||
id: 4,
|
||||
name: r'localId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'modifiedAt': PropertySchema(
|
||||
id: 6,
|
||||
id: 5,
|
||||
name: r'modifiedAt',
|
||||
type: IsarType.dateTime,
|
||||
),
|
||||
r'name': PropertySchema(
|
||||
id: 7,
|
||||
id: 6,
|
||||
name: r'name',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'remoteId': PropertySchema(
|
||||
id: 8,
|
||||
id: 7,
|
||||
name: r'remoteId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'shared': PropertySchema(
|
||||
id: 9,
|
||||
id: 8,
|
||||
name: r'shared',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'sortOrder': PropertySchema(
|
||||
id: 10,
|
||||
id: 9,
|
||||
name: r'sortOrder',
|
||||
type: IsarType.byte,
|
||||
enumMap: _AlbumsortOrderEnumValueMap,
|
||||
),
|
||||
r'startDate': PropertySchema(
|
||||
id: 11,
|
||||
id: 10,
|
||||
name: r'startDate',
|
||||
type: IsarType.dateTime,
|
||||
)
|
||||
@@ -151,12 +146,6 @@ int _albumEstimateSize(
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
var bytesCount = offsets.last;
|
||||
{
|
||||
final value = object.description;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
{
|
||||
final value = object.localId;
|
||||
if (value != null) {
|
||||
@@ -181,16 +170,15 @@ void _albumSerialize(
|
||||
) {
|
||||
writer.writeBool(offsets[0], object.activityEnabled);
|
||||
writer.writeDateTime(offsets[1], object.createdAt);
|
||||
writer.writeString(offsets[2], object.description);
|
||||
writer.writeDateTime(offsets[3], object.endDate);
|
||||
writer.writeDateTime(offsets[4], object.lastModifiedAssetTimestamp);
|
||||
writer.writeString(offsets[5], object.localId);
|
||||
writer.writeDateTime(offsets[6], object.modifiedAt);
|
||||
writer.writeString(offsets[7], object.name);
|
||||
writer.writeString(offsets[8], object.remoteId);
|
||||
writer.writeBool(offsets[9], object.shared);
|
||||
writer.writeByte(offsets[10], object.sortOrder.index);
|
||||
writer.writeDateTime(offsets[11], object.startDate);
|
||||
writer.writeDateTime(offsets[2], object.endDate);
|
||||
writer.writeDateTime(offsets[3], object.lastModifiedAssetTimestamp);
|
||||
writer.writeString(offsets[4], object.localId);
|
||||
writer.writeDateTime(offsets[5], object.modifiedAt);
|
||||
writer.writeString(offsets[6], object.name);
|
||||
writer.writeString(offsets[7], object.remoteId);
|
||||
writer.writeBool(offsets[8], object.shared);
|
||||
writer.writeByte(offsets[9], object.sortOrder.index);
|
||||
writer.writeDateTime(offsets[10], object.startDate);
|
||||
}
|
||||
|
||||
Album _albumDeserialize(
|
||||
@@ -202,18 +190,16 @@ Album _albumDeserialize(
|
||||
final object = Album(
|
||||
activityEnabled: reader.readBool(offsets[0]),
|
||||
createdAt: reader.readDateTime(offsets[1]),
|
||||
description: reader.readStringOrNull(offsets[2]),
|
||||
endDate: reader.readDateTimeOrNull(offsets[3]),
|
||||
lastModifiedAssetTimestamp: reader.readDateTimeOrNull(offsets[4]),
|
||||
localId: reader.readStringOrNull(offsets[5]),
|
||||
modifiedAt: reader.readDateTime(offsets[6]),
|
||||
name: reader.readString(offsets[7]),
|
||||
remoteId: reader.readStringOrNull(offsets[8]),
|
||||
shared: reader.readBool(offsets[9]),
|
||||
sortOrder:
|
||||
_AlbumsortOrderValueEnumMap[reader.readByteOrNull(offsets[10])] ??
|
||||
SortOrder.desc,
|
||||
startDate: reader.readDateTimeOrNull(offsets[11]),
|
||||
endDate: reader.readDateTimeOrNull(offsets[2]),
|
||||
lastModifiedAssetTimestamp: reader.readDateTimeOrNull(offsets[3]),
|
||||
localId: reader.readStringOrNull(offsets[4]),
|
||||
modifiedAt: reader.readDateTime(offsets[5]),
|
||||
name: reader.readString(offsets[6]),
|
||||
remoteId: reader.readStringOrNull(offsets[7]),
|
||||
shared: reader.readBool(offsets[8]),
|
||||
sortOrder: _AlbumsortOrderValueEnumMap[reader.readByteOrNull(offsets[9])] ??
|
||||
SortOrder.desc,
|
||||
startDate: reader.readDateTimeOrNull(offsets[10]),
|
||||
);
|
||||
object.id = id;
|
||||
return object;
|
||||
@@ -231,25 +217,23 @@ P _albumDeserializeProp<P>(
|
||||
case 1:
|
||||
return (reader.readDateTime(offset)) as P;
|
||||
case 2:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
return (reader.readDateTimeOrNull(offset)) as P;
|
||||
case 3:
|
||||
return (reader.readDateTimeOrNull(offset)) as P;
|
||||
case 4:
|
||||
return (reader.readDateTimeOrNull(offset)) as P;
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 5:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 6:
|
||||
return (reader.readDateTime(offset)) as P;
|
||||
case 7:
|
||||
case 6:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 8:
|
||||
case 7:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 9:
|
||||
case 8:
|
||||
return (reader.readBool(offset)) as P;
|
||||
case 10:
|
||||
case 9:
|
||||
return (_AlbumsortOrderValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||
SortOrder.desc) as P;
|
||||
case 11:
|
||||
case 10:
|
||||
return (reader.readDateTimeOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
@@ -551,152 +535,6 @@ extension AlbumQueryFilter on QueryBuilder<Album, Album, QFilterCondition> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'description',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'description',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'description',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'description',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'description',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionBetween(
|
||||
String? lower,
|
||||
String? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'description',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'description',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'description',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionContains(
|
||||
String value,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'description',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionMatches(
|
||||
String pattern,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'description',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'description',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'description',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterFilterCondition> endDateIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
@@ -1664,18 +1502,6 @@ extension AlbumQuerySortBy on QueryBuilder<Album, Album, QSortBy> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterSortBy> sortByDescription() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'description', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterSortBy> sortByDescriptionDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'description', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterSortBy> sortByEndDate() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'endDate', Sort.asc);
|
||||
@@ -1811,18 +1637,6 @@ extension AlbumQuerySortThenBy on QueryBuilder<Album, Album, QSortThenBy> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterSortBy> thenByDescription() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'description', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterSortBy> thenByDescriptionDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'description', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QAfterSortBy> thenByEndDate() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'endDate', Sort.asc);
|
||||
@@ -1958,13 +1772,6 @@ extension AlbumQueryWhereDistinct on QueryBuilder<Album, Album, QDistinct> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QDistinct> distinctByDescription(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'description', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, Album, QDistinct> distinctByEndDate() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'endDate');
|
||||
@@ -2042,12 +1849,6 @@ extension AlbumQueryProperty on QueryBuilder<Album, Album, QQueryProperty> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, String?, QQueryOperations> descriptionProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'description');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Album, DateTime?, QQueryOperations> endDateProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'endDate');
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:uuid/parsing.dart';
|
||||
|
||||
extension StringExtension on String {
|
||||
String capitalize() {
|
||||
return split(" ")
|
||||
@@ -29,3 +33,8 @@ extension DurationExtension on String {
|
||||
return int.parse(this);
|
||||
}
|
||||
}
|
||||
|
||||
extension UUIDExtension on String {
|
||||
Uint8List toUuidByte({bool shouldValidate = false}) =>
|
||||
UuidParsing.parseAsByteList(this, validate: shouldValidate);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import 'package:drift/drift.dart' hide Query;
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart' as domain;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
@@ -93,53 +90,3 @@ class ExifInfo {
|
||||
exposureSeconds: exposureSeconds,
|
||||
);
|
||||
}
|
||||
|
||||
class RemoteExifEntity extends Table with DriftDefaultsMixin {
|
||||
const RemoteExifEntity();
|
||||
|
||||
TextColumn get assetId =>
|
||||
text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
TextColumn get city => text().nullable()();
|
||||
|
||||
TextColumn get state => text().nullable()();
|
||||
|
||||
TextColumn get country => text().nullable()();
|
||||
|
||||
DateTimeColumn get dateTimeOriginal => dateTime().nullable()();
|
||||
|
||||
TextColumn get description => text().nullable()();
|
||||
|
||||
IntColumn get height => integer().nullable()();
|
||||
|
||||
IntColumn get width => integer().nullable()();
|
||||
|
||||
TextColumn get exposureTime => text().nullable()();
|
||||
|
||||
IntColumn get fNumber => integer().nullable()();
|
||||
|
||||
IntColumn get fileSize => integer().nullable()();
|
||||
|
||||
IntColumn get focalLength => integer().nullable()();
|
||||
|
||||
IntColumn get latitude => integer().nullable()();
|
||||
|
||||
IntColumn get longitude => integer().nullable()();
|
||||
|
||||
IntColumn get iso => integer().nullable()();
|
||||
|
||||
TextColumn get make => text().nullable()();
|
||||
|
||||
TextColumn get model => text().nullable()();
|
||||
|
||||
TextColumn get orientation => text().nullable()();
|
||||
|
||||
TextColumn get timeZone => text().nullable()();
|
||||
|
||||
IntColumn get rating => integer().nullable()();
|
||||
|
||||
TextColumn get projectionType => text().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {assetId};
|
||||
}
|
||||
|
||||
1484
mobile/lib/infrastructure/entities/exif.entity.drift.dart
generated
1484
mobile/lib/infrastructure/entities/exif.entity.drift.dart
generated
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,6 @@ class LocalAlbumEntity extends Table with DriftDefaultsMixin {
|
||||
TextColumn get name => text()();
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
IntColumn get backupSelection => intEnum<BackupSelection>()();
|
||||
BoolColumn get isIosSharedAlbum =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
// Used for mark & sweep
|
||||
BoolColumn get marker_ => boolean().nullable()();
|
||||
|
||||
@@ -14,7 +14,6 @@ typedef $$LocalAlbumEntityTableCreateCompanionBuilder
|
||||
required String name,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
required i2.BackupSelection backupSelection,
|
||||
i0.Value<bool> isIosSharedAlbum,
|
||||
i0.Value<bool?> marker_,
|
||||
});
|
||||
typedef $$LocalAlbumEntityTableUpdateCompanionBuilder
|
||||
@@ -23,7 +22,6 @@ typedef $$LocalAlbumEntityTableUpdateCompanionBuilder
|
||||
i0.Value<String> name,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
i0.Value<i2.BackupSelection> backupSelection,
|
||||
i0.Value<bool> isIosSharedAlbum,
|
||||
i0.Value<bool?> marker_,
|
||||
});
|
||||
|
||||
@@ -50,10 +48,6 @@ class $$LocalAlbumEntityTableFilterComposer
|
||||
column: $table.backupSelection,
|
||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
|
||||
|
||||
i0.ColumnFilters<bool> get isIosSharedAlbum => $composableBuilder(
|
||||
column: $table.isIosSharedAlbum,
|
||||
builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<bool> get marker_ => $composableBuilder(
|
||||
column: $table.marker_, builder: (column) => i0.ColumnFilters(column));
|
||||
}
|
||||
@@ -81,10 +75,6 @@ class $$LocalAlbumEntityTableOrderingComposer
|
||||
column: $table.backupSelection,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<bool> get isIosSharedAlbum => $composableBuilder(
|
||||
column: $table.isIosSharedAlbum,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<bool> get marker_ => $composableBuilder(
|
||||
column: $table.marker_, builder: (column) => i0.ColumnOrderings(column));
|
||||
}
|
||||
@@ -111,9 +101,6 @@ class $$LocalAlbumEntityTableAnnotationComposer
|
||||
get backupSelection => $composableBuilder(
|
||||
column: $table.backupSelection, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<bool> get isIosSharedAlbum => $composableBuilder(
|
||||
column: $table.isIosSharedAlbum, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<bool> get marker_ =>
|
||||
$composableBuilder(column: $table.marker_, builder: (column) => column);
|
||||
}
|
||||
@@ -152,7 +139,6 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
i0.Value<i2.BackupSelection> backupSelection =
|
||||
const i0.Value.absent(),
|
||||
i0.Value<bool> isIosSharedAlbum = const i0.Value.absent(),
|
||||
i0.Value<bool?> marker_ = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.LocalAlbumEntityCompanion(
|
||||
@@ -160,7 +146,6 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
||||
name: name,
|
||||
updatedAt: updatedAt,
|
||||
backupSelection: backupSelection,
|
||||
isIosSharedAlbum: isIosSharedAlbum,
|
||||
marker_: marker_,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
@@ -168,7 +153,6 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
||||
required String name,
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
required i2.BackupSelection backupSelection,
|
||||
i0.Value<bool> isIosSharedAlbum = const i0.Value.absent(),
|
||||
i0.Value<bool?> marker_ = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.LocalAlbumEntityCompanion.insert(
|
||||
@@ -176,7 +160,6 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
||||
name: name,
|
||||
updatedAt: updatedAt,
|
||||
backupSelection: backupSelection,
|
||||
isIosSharedAlbum: isIosSharedAlbum,
|
||||
marker_: marker_,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
@@ -235,16 +218,6 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
||||
type: i0.DriftSqlType.int, requiredDuringInsert: true)
|
||||
.withConverter<i2.BackupSelection>(
|
||||
i1.$LocalAlbumEntityTable.$converterbackupSelection);
|
||||
static const i0.VerificationMeta _isIosSharedAlbumMeta =
|
||||
const i0.VerificationMeta('isIosSharedAlbum');
|
||||
@override
|
||||
late final i0.GeneratedColumn<bool> isIosSharedAlbum =
|
||||
i0.GeneratedColumn<bool>('is_ios_shared_album', aliasedName, false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("is_ios_shared_album" IN (0, 1))'),
|
||||
defaultValue: const i4.Constant(false));
|
||||
static const i0.VerificationMeta _marker_Meta =
|
||||
const i0.VerificationMeta('marker_');
|
||||
@override
|
||||
@@ -256,7 +229,7 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
||||
i0.GeneratedColumn.constraintIsAlways('CHECK ("marker" IN (0, 1))'));
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns =>
|
||||
[id, name, updatedAt, backupSelection, isIosSharedAlbum, marker_];
|
||||
[id, name, updatedAt, backupSelection, marker_];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
@@ -283,12 +256,6 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
||||
context.handle(_updatedAtMeta,
|
||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta));
|
||||
}
|
||||
if (data.containsKey('is_ios_shared_album')) {
|
||||
context.handle(
|
||||
_isIosSharedAlbumMeta,
|
||||
isIosSharedAlbum.isAcceptableOrUnknown(
|
||||
data['is_ios_shared_album']!, _isIosSharedAlbumMeta));
|
||||
}
|
||||
if (data.containsKey('marker')) {
|
||||
context.handle(_marker_Meta,
|
||||
marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta));
|
||||
@@ -312,8 +279,6 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
||||
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
||||
.fromSql(attachedDatabase.typeMapping.read(i0.DriftSqlType.int,
|
||||
data['${effectivePrefix}backup_selection'])!),
|
||||
isIosSharedAlbum: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool, data['${effectivePrefix}is_ios_shared_album'])!,
|
||||
marker_: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.bool, data['${effectivePrefix}marker']),
|
||||
);
|
||||
@@ -340,14 +305,12 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
final String name;
|
||||
final DateTime updatedAt;
|
||||
final i2.BackupSelection backupSelection;
|
||||
final bool isIosSharedAlbum;
|
||||
final bool? marker_;
|
||||
const LocalAlbumEntityData(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.updatedAt,
|
||||
required this.backupSelection,
|
||||
required this.isIosSharedAlbum,
|
||||
this.marker_});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
@@ -360,7 +323,6 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||
.toSql(backupSelection));
|
||||
}
|
||||
map['is_ios_shared_album'] = i0.Variable<bool>(isIosSharedAlbum);
|
||||
if (!nullToAbsent || marker_ != null) {
|
||||
map['marker'] = i0.Variable<bool>(marker_);
|
||||
}
|
||||
@@ -376,7 +338,6 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
||||
.fromJson(serializer.fromJson<int>(json['backupSelection'])),
|
||||
isIosSharedAlbum: serializer.fromJson<bool>(json['isIosSharedAlbum']),
|
||||
marker_: serializer.fromJson<bool?>(json['marker_']),
|
||||
);
|
||||
}
|
||||
@@ -390,7 +351,6 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
'backupSelection': serializer.toJson<int>(i1
|
||||
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||
.toJson(backupSelection)),
|
||||
'isIosSharedAlbum': serializer.toJson<bool>(isIosSharedAlbum),
|
||||
'marker_': serializer.toJson<bool?>(marker_),
|
||||
};
|
||||
}
|
||||
@@ -400,14 +360,12 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
String? name,
|
||||
DateTime? updatedAt,
|
||||
i2.BackupSelection? backupSelection,
|
||||
bool? isIosSharedAlbum,
|
||||
i0.Value<bool?> marker_ = const i0.Value.absent()}) =>
|
||||
i1.LocalAlbumEntityData(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
backupSelection: backupSelection ?? this.backupSelection,
|
||||
isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum,
|
||||
marker_: marker_.present ? marker_.value : this.marker_,
|
||||
);
|
||||
LocalAlbumEntityData copyWithCompanion(i1.LocalAlbumEntityCompanion data) {
|
||||
@@ -418,9 +376,6 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
backupSelection: data.backupSelection.present
|
||||
? data.backupSelection.value
|
||||
: this.backupSelection,
|
||||
isIosSharedAlbum: data.isIosSharedAlbum.present
|
||||
? data.isIosSharedAlbum.value
|
||||
: this.isIosSharedAlbum,
|
||||
marker_: data.marker_.present ? data.marker_.value : this.marker_,
|
||||
);
|
||||
}
|
||||
@@ -432,15 +387,14 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
..write('name: $name, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('backupSelection: $backupSelection, ')
|
||||
..write('isIosSharedAlbum: $isIosSharedAlbum, ')
|
||||
..write('marker_: $marker_')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
id, name, updatedAt, backupSelection, isIosSharedAlbum, marker_);
|
||||
int get hashCode =>
|
||||
Object.hash(id, name, updatedAt, backupSelection, marker_);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@@ -449,7 +403,6 @@ class LocalAlbumEntityData extends i0.DataClass
|
||||
other.name == this.name &&
|
||||
other.updatedAt == this.updatedAt &&
|
||||
other.backupSelection == this.backupSelection &&
|
||||
other.isIosSharedAlbum == this.isIosSharedAlbum &&
|
||||
other.marker_ == this.marker_);
|
||||
}
|
||||
|
||||
@@ -459,14 +412,12 @@ class LocalAlbumEntityCompanion
|
||||
final i0.Value<String> name;
|
||||
final i0.Value<DateTime> updatedAt;
|
||||
final i0.Value<i2.BackupSelection> backupSelection;
|
||||
final i0.Value<bool> isIosSharedAlbum;
|
||||
final i0.Value<bool?> marker_;
|
||||
const LocalAlbumEntityCompanion({
|
||||
this.id = const i0.Value.absent(),
|
||||
this.name = const i0.Value.absent(),
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
this.backupSelection = const i0.Value.absent(),
|
||||
this.isIosSharedAlbum = const i0.Value.absent(),
|
||||
this.marker_ = const i0.Value.absent(),
|
||||
});
|
||||
LocalAlbumEntityCompanion.insert({
|
||||
@@ -474,7 +425,6 @@ class LocalAlbumEntityCompanion
|
||||
required String name,
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
required i2.BackupSelection backupSelection,
|
||||
this.isIosSharedAlbum = const i0.Value.absent(),
|
||||
this.marker_ = const i0.Value.absent(),
|
||||
}) : id = i0.Value(id),
|
||||
name = i0.Value(name),
|
||||
@@ -484,7 +434,6 @@ class LocalAlbumEntityCompanion
|
||||
i0.Expression<String>? name,
|
||||
i0.Expression<DateTime>? updatedAt,
|
||||
i0.Expression<int>? backupSelection,
|
||||
i0.Expression<bool>? isIosSharedAlbum,
|
||||
i0.Expression<bool>? marker_,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
@@ -492,7 +441,6 @@ class LocalAlbumEntityCompanion
|
||||
if (name != null) 'name': name,
|
||||
if (updatedAt != null) 'updated_at': updatedAt,
|
||||
if (backupSelection != null) 'backup_selection': backupSelection,
|
||||
if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum,
|
||||
if (marker_ != null) 'marker': marker_,
|
||||
});
|
||||
}
|
||||
@@ -502,14 +450,12 @@ class LocalAlbumEntityCompanion
|
||||
i0.Value<String>? name,
|
||||
i0.Value<DateTime>? updatedAt,
|
||||
i0.Value<i2.BackupSelection>? backupSelection,
|
||||
i0.Value<bool>? isIosSharedAlbum,
|
||||
i0.Value<bool?>? marker_}) {
|
||||
return i1.LocalAlbumEntityCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
backupSelection: backupSelection ?? this.backupSelection,
|
||||
isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum,
|
||||
marker_: marker_ ?? this.marker_,
|
||||
);
|
||||
}
|
||||
@@ -531,9 +477,6 @@ class LocalAlbumEntityCompanion
|
||||
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||
.toSql(backupSelection.value));
|
||||
}
|
||||
if (isIosSharedAlbum.present) {
|
||||
map['is_ios_shared_album'] = i0.Variable<bool>(isIosSharedAlbum.value);
|
||||
}
|
||||
if (marker_.present) {
|
||||
map['marker'] = i0.Variable<bool>(marker_.value);
|
||||
}
|
||||
@@ -547,7 +490,6 @@ class LocalAlbumEntityCompanion
|
||||
..write('name: $name, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('backupSelection: $backupSelection, ')
|
||||
..write('isIosSharedAlbum: $isIosSharedAlbum, ')
|
||||
..write('marker_: $marker_')
|
||||
..write(')'))
|
||||
.toString();
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
@TableIndex(name: 'idx_local_asset_checksum', columns: {#checksum})
|
||||
@TableIndex(name: 'local_asset_checksum', columns: {#checksum})
|
||||
class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||
const LocalAssetEntity();
|
||||
|
||||
|
||||
@@ -231,8 +231,8 @@ typedef $$LocalAssetEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||
),
|
||||
i1.LocalAssetEntityData,
|
||||
i0.PrefetchHooks Function()>;
|
||||
i0.Index get idxLocalAssetChecksum => i0.Index('idx_local_asset_checksum',
|
||||
'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)');
|
||||
i0.Index get localAssetChecksum => i0.Index('local_asset_checksum',
|
||||
'CREATE INDEX local_asset_checksum ON local_asset_entity (checksum)');
|
||||
|
||||
class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
with i0.TableInfo<$LocalAssetEntityTable, i1.LocalAssetEntityData> {
|
||||
|
||||
@@ -5,11 +5,11 @@ import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
class PartnerEntity extends Table with DriftDefaultsMixin {
|
||||
const PartnerEntity();
|
||||
|
||||
TextColumn get sharedById =>
|
||||
text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
BlobColumn get sharedById =>
|
||||
blob().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
TextColumn get sharedWithId =>
|
||||
text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
BlobColumn get sharedWithId =>
|
||||
blob().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
BoolColumn get inTimeline => boolean().withDefault(const Constant(false))();
|
||||
|
||||
|
||||
@@ -3,23 +3,24 @@
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
as i1;
|
||||
import 'dart:typed_data' as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'
|
||||
as i2;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
||||
as i3;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i4;
|
||||
import 'package:drift/internal/modular.dart' as i5;
|
||||
as i5;
|
||||
import 'package:drift/internal/modular.dart' as i6;
|
||||
|
||||
typedef $$PartnerEntityTableCreateCompanionBuilder = i1.PartnerEntityCompanion
|
||||
Function({
|
||||
required String sharedById,
|
||||
required String sharedWithId,
|
||||
required i2.Uint8List sharedById,
|
||||
required i2.Uint8List sharedWithId,
|
||||
i0.Value<bool> inTimeline,
|
||||
});
|
||||
typedef $$PartnerEntityTableUpdateCompanionBuilder = i1.PartnerEntityCompanion
|
||||
Function({
|
||||
i0.Value<String> sharedById,
|
||||
i0.Value<String> sharedWithId,
|
||||
i0.Value<i2.Uint8List> sharedById,
|
||||
i0.Value<i2.Uint8List> sharedWithId,
|
||||
i0.Value<bool> inTimeline,
|
||||
});
|
||||
|
||||
@@ -28,25 +29,25 @@ final class $$PartnerEntityTableReferences extends i0.BaseReferences<
|
||||
$$PartnerEntityTableReferences(
|
||||
super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static i4.$UserEntityTable _sharedByIdTable(i0.GeneratedDatabase db) =>
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
static i5.$UserEntityTable _sharedByIdTable(i0.GeneratedDatabase db) =>
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i5.ReadDatabaseContainer(db)
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$PartnerEntityTable>('partner_entity')
|
||||
.sharedById,
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.id));
|
||||
|
||||
i4.$$UserEntityTableProcessedTableManager get sharedById {
|
||||
final $_column = $_itemColumn<String>('shared_by_id')!;
|
||||
i5.$$UserEntityTableProcessedTableManager get sharedById {
|
||||
final $_column = $_itemColumn<i2.Uint8List>('shared_by_id')!;
|
||||
|
||||
final manager = i4
|
||||
final manager = i5
|
||||
.$$UserEntityTableTableManager(
|
||||
$_db,
|
||||
i5.ReadDatabaseContainer($_db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'))
|
||||
i6.ReadDatabaseContainer($_db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_sharedByIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
@@ -54,25 +55,25 @@ final class $$PartnerEntityTableReferences extends i0.BaseReferences<
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
|
||||
static i4.$UserEntityTable _sharedWithIdTable(i0.GeneratedDatabase db) =>
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
static i5.$UserEntityTable _sharedWithIdTable(i0.GeneratedDatabase db) =>
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i5.ReadDatabaseContainer(db)
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$PartnerEntityTable>('partner_entity')
|
||||
.sharedWithId,
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.id));
|
||||
|
||||
i4.$$UserEntityTableProcessedTableManager get sharedWithId {
|
||||
final $_column = $_itemColumn<String>('shared_with_id')!;
|
||||
i5.$$UserEntityTableProcessedTableManager get sharedWithId {
|
||||
final $_column = $_itemColumn<i2.Uint8List>('shared_with_id')!;
|
||||
|
||||
final manager = i4
|
||||
final manager = i5
|
||||
.$$UserEntityTableTableManager(
|
||||
$_db,
|
||||
i5.ReadDatabaseContainer($_db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'))
|
||||
i6.ReadDatabaseContainer($_db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_sharedWithIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
@@ -93,20 +94,20 @@ class $$PartnerEntityTableFilterComposer
|
||||
i0.ColumnFilters<bool> get inTimeline => $composableBuilder(
|
||||
column: $table.inTimeline, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i4.$$UserEntityTableFilterComposer get sharedById {
|
||||
final i4.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableFilterComposer get sharedById {
|
||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedById,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableFilterComposer(
|
||||
i5.$$UserEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -115,20 +116,20 @@ class $$PartnerEntityTableFilterComposer
|
||||
return composer;
|
||||
}
|
||||
|
||||
i4.$$UserEntityTableFilterComposer get sharedWithId {
|
||||
final i4.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableFilterComposer get sharedWithId {
|
||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedWithId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableFilterComposer(
|
||||
i5.$$UserEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -151,20 +152,20 @@ class $$PartnerEntityTableOrderingComposer
|
||||
column: $table.inTimeline,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i4.$$UserEntityTableOrderingComposer get sharedById {
|
||||
final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableOrderingComposer get sharedById {
|
||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedById,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableOrderingComposer(
|
||||
i5.$$UserEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -173,20 +174,20 @@ class $$PartnerEntityTableOrderingComposer
|
||||
return composer;
|
||||
}
|
||||
|
||||
i4.$$UserEntityTableOrderingComposer get sharedWithId {
|
||||
final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableOrderingComposer get sharedWithId {
|
||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedWithId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableOrderingComposer(
|
||||
i5.$$UserEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -208,20 +209,20 @@ class $$PartnerEntityTableAnnotationComposer
|
||||
i0.GeneratedColumn<bool> get inTimeline => $composableBuilder(
|
||||
column: $table.inTimeline, builder: (column) => column);
|
||||
|
||||
i4.$$UserEntityTableAnnotationComposer get sharedById {
|
||||
final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableAnnotationComposer get sharedById {
|
||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedById,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableAnnotationComposer(
|
||||
i5.$$UserEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -230,20 +231,20 @@ class $$PartnerEntityTableAnnotationComposer
|
||||
return composer;
|
||||
}
|
||||
|
||||
i4.$$UserEntityTableAnnotationComposer get sharedWithId {
|
||||
final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableAnnotationComposer get sharedWithId {
|
||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedWithId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableAnnotationComposer(
|
||||
i5.$$UserEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -277,8 +278,8 @@ class $$PartnerEntityTableTableManager extends i0.RootTableManager<
|
||||
createComputedFieldComposer: () =>
|
||||
i1.$$PartnerEntityTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
i0.Value<String> sharedById = const i0.Value.absent(),
|
||||
i0.Value<String> sharedWithId = const i0.Value.absent(),
|
||||
i0.Value<i2.Uint8List> sharedById = const i0.Value.absent(),
|
||||
i0.Value<i2.Uint8List> sharedWithId = const i0.Value.absent(),
|
||||
i0.Value<bool> inTimeline = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.PartnerEntityCompanion(
|
||||
@@ -287,8 +288,8 @@ class $$PartnerEntityTableTableManager extends i0.RootTableManager<
|
||||
inTimeline: inTimeline,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String sharedById,
|
||||
required String sharedWithId,
|
||||
required i2.Uint8List sharedById,
|
||||
required i2.Uint8List sharedWithId,
|
||||
i0.Value<bool> inTimeline = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.PartnerEntityCompanion.insert(
|
||||
@@ -365,7 +366,7 @@ typedef $$PartnerEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||
i1.PartnerEntityData,
|
||||
i0.PrefetchHooks Function({bool sharedById, bool sharedWithId})>;
|
||||
|
||||
class $PartnerEntityTable extends i2.PartnerEntity
|
||||
class $PartnerEntityTable extends i3.PartnerEntity
|
||||
with i0.TableInfo<$PartnerEntityTable, i1.PartnerEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
@@ -374,18 +375,18 @@ class $PartnerEntityTable extends i2.PartnerEntity
|
||||
static const i0.VerificationMeta _sharedByIdMeta =
|
||||
const i0.VerificationMeta('sharedById');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> sharedById = i0.GeneratedColumn<String>(
|
||||
'shared_by_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
late final i0.GeneratedColumn<i2.Uint8List> sharedById =
|
||||
i0.GeneratedColumn<i2.Uint8List>('shared_by_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
static const i0.VerificationMeta _sharedWithIdMeta =
|
||||
const i0.VerificationMeta('sharedWithId');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> sharedWithId =
|
||||
i0.GeneratedColumn<String>('shared_with_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.string,
|
||||
late final i0.GeneratedColumn<i2.Uint8List> sharedWithId =
|
||||
i0.GeneratedColumn<i2.Uint8List>('shared_with_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
@@ -398,7 +399,7 @@ class $PartnerEntityTable extends i2.PartnerEntity
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("in_timeline" IN (0, 1))'),
|
||||
defaultValue: const i3.Constant(false));
|
||||
defaultValue: const i4.Constant(false));
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns =>
|
||||
[sharedById, sharedWithId, inTimeline];
|
||||
@@ -444,10 +445,10 @@ class $PartnerEntityTable extends i2.PartnerEntity
|
||||
i1.PartnerEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.PartnerEntityData(
|
||||
sharedById: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string, data['${effectivePrefix}shared_by_id'])!,
|
||||
sharedById: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.blob, data['${effectivePrefix}shared_by_id'])!,
|
||||
sharedWithId: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string, data['${effectivePrefix}shared_with_id'])!,
|
||||
i0.DriftSqlType.blob, data['${effectivePrefix}shared_with_id'])!,
|
||||
inTimeline: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.bool, data['${effectivePrefix}in_timeline'])!,
|
||||
);
|
||||
@@ -466,8 +467,8 @@ class $PartnerEntityTable extends i2.PartnerEntity
|
||||
|
||||
class PartnerEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.PartnerEntityData> {
|
||||
final String sharedById;
|
||||
final String sharedWithId;
|
||||
final i2.Uint8List sharedById;
|
||||
final i2.Uint8List sharedWithId;
|
||||
final bool inTimeline;
|
||||
const PartnerEntityData(
|
||||
{required this.sharedById,
|
||||
@@ -476,8 +477,8 @@ class PartnerEntityData extends i0.DataClass
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['shared_by_id'] = i0.Variable<String>(sharedById);
|
||||
map['shared_with_id'] = i0.Variable<String>(sharedWithId);
|
||||
map['shared_by_id'] = i0.Variable<i2.Uint8List>(sharedById);
|
||||
map['shared_with_id'] = i0.Variable<i2.Uint8List>(sharedWithId);
|
||||
map['in_timeline'] = i0.Variable<bool>(inTimeline);
|
||||
return map;
|
||||
}
|
||||
@@ -486,8 +487,8 @@ class PartnerEntityData extends i0.DataClass
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return PartnerEntityData(
|
||||
sharedById: serializer.fromJson<String>(json['sharedById']),
|
||||
sharedWithId: serializer.fromJson<String>(json['sharedWithId']),
|
||||
sharedById: serializer.fromJson<i2.Uint8List>(json['sharedById']),
|
||||
sharedWithId: serializer.fromJson<i2.Uint8List>(json['sharedWithId']),
|
||||
inTimeline: serializer.fromJson<bool>(json['inTimeline']),
|
||||
);
|
||||
}
|
||||
@@ -495,14 +496,16 @@ class PartnerEntityData extends i0.DataClass
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'sharedById': serializer.toJson<String>(sharedById),
|
||||
'sharedWithId': serializer.toJson<String>(sharedWithId),
|
||||
'sharedById': serializer.toJson<i2.Uint8List>(sharedById),
|
||||
'sharedWithId': serializer.toJson<i2.Uint8List>(sharedWithId),
|
||||
'inTimeline': serializer.toJson<bool>(inTimeline),
|
||||
};
|
||||
}
|
||||
|
||||
i1.PartnerEntityData copyWith(
|
||||
{String? sharedById, String? sharedWithId, bool? inTimeline}) =>
|
||||
{i2.Uint8List? sharedById,
|
||||
i2.Uint8List? sharedWithId,
|
||||
bool? inTimeline}) =>
|
||||
i1.PartnerEntityData(
|
||||
sharedById: sharedById ?? this.sharedById,
|
||||
sharedWithId: sharedWithId ?? this.sharedWithId,
|
||||
@@ -531,19 +534,20 @@ class PartnerEntityData extends i0.DataClass
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline);
|
||||
int get hashCode => Object.hash(i0.$driftBlobEquality.hash(sharedById),
|
||||
i0.$driftBlobEquality.hash(sharedWithId), inTimeline);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.PartnerEntityData &&
|
||||
other.sharedById == this.sharedById &&
|
||||
other.sharedWithId == this.sharedWithId &&
|
||||
i0.$driftBlobEquality.equals(other.sharedById, this.sharedById) &&
|
||||
i0.$driftBlobEquality.equals(other.sharedWithId, this.sharedWithId) &&
|
||||
other.inTimeline == this.inTimeline);
|
||||
}
|
||||
|
||||
class PartnerEntityCompanion extends i0.UpdateCompanion<i1.PartnerEntityData> {
|
||||
final i0.Value<String> sharedById;
|
||||
final i0.Value<String> sharedWithId;
|
||||
final i0.Value<i2.Uint8List> sharedById;
|
||||
final i0.Value<i2.Uint8List> sharedWithId;
|
||||
final i0.Value<bool> inTimeline;
|
||||
const PartnerEntityCompanion({
|
||||
this.sharedById = const i0.Value.absent(),
|
||||
@@ -551,14 +555,14 @@ class PartnerEntityCompanion extends i0.UpdateCompanion<i1.PartnerEntityData> {
|
||||
this.inTimeline = const i0.Value.absent(),
|
||||
});
|
||||
PartnerEntityCompanion.insert({
|
||||
required String sharedById,
|
||||
required String sharedWithId,
|
||||
required i2.Uint8List sharedById,
|
||||
required i2.Uint8List sharedWithId,
|
||||
this.inTimeline = const i0.Value.absent(),
|
||||
}) : sharedById = i0.Value(sharedById),
|
||||
sharedWithId = i0.Value(sharedWithId);
|
||||
static i0.Insertable<i1.PartnerEntityData> custom({
|
||||
i0.Expression<String>? sharedById,
|
||||
i0.Expression<String>? sharedWithId,
|
||||
i0.Expression<i2.Uint8List>? sharedById,
|
||||
i0.Expression<i2.Uint8List>? sharedWithId,
|
||||
i0.Expression<bool>? inTimeline,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
@@ -569,8 +573,8 @@ class PartnerEntityCompanion extends i0.UpdateCompanion<i1.PartnerEntityData> {
|
||||
}
|
||||
|
||||
i1.PartnerEntityCompanion copyWith(
|
||||
{i0.Value<String>? sharedById,
|
||||
i0.Value<String>? sharedWithId,
|
||||
{i0.Value<i2.Uint8List>? sharedById,
|
||||
i0.Value<i2.Uint8List>? sharedWithId,
|
||||
i0.Value<bool>? inTimeline}) {
|
||||
return i1.PartnerEntityCompanion(
|
||||
sharedById: sharedById ?? this.sharedById,
|
||||
@@ -583,10 +587,10 @@ class PartnerEntityCompanion extends i0.UpdateCompanion<i1.PartnerEntityData> {
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (sharedById.present) {
|
||||
map['shared_by_id'] = i0.Variable<String>(sharedById.value);
|
||||
map['shared_by_id'] = i0.Variable<i2.Uint8List>(sharedById.value);
|
||||
}
|
||||
if (sharedWithId.present) {
|
||||
map['shared_with_id'] = i0.Variable<String>(sharedWithId.value);
|
||||
map['shared_with_id'] = i0.Variable<i2.Uint8List>(sharedWithId.value);
|
||||
}
|
||||
if (inTimeline.present) {
|
||||
map['in_timeline'] = i0.Variable<bool>(inTimeline.value);
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
@TableIndex(
|
||||
name: 'UQ_remote_asset_owner_checksum',
|
||||
columns: {#checksum, #ownerId},
|
||||
unique: true,
|
||||
)
|
||||
class RemoteAssetEntity extends Table
|
||||
with DriftDefaultsMixin, AssetEntityMixin {
|
||||
const RemoteAssetEntity();
|
||||
|
||||
TextColumn get id => text()();
|
||||
|
||||
TextColumn get checksum => text()();
|
||||
|
||||
BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
|
||||
|
||||
TextColumn get ownerId =>
|
||||
text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
DateTimeColumn get localDateTime => dateTime().nullable()();
|
||||
|
||||
TextColumn get thumbHash => text().nullable()();
|
||||
|
||||
DateTimeColumn get deletedAt => dateTime().nullable()();
|
||||
|
||||
IntColumn get visibility => intEnum<AssetVisibility>()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,7 @@ class User {
|
||||
class UserEntity extends Table with DriftDefaultsMixin {
|
||||
const UserEntity();
|
||||
|
||||
TextColumn get id => text()();
|
||||
BlobColumn get id => blob()();
|
||||
TextColumn get name => text()();
|
||||
BoolColumn get isAdmin => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get email => text()();
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
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 'dart:typed_data' 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 i2.Uint8List id,
|
||||
required String name,
|
||||
i0.Value<bool> isAdmin,
|
||||
required String email,
|
||||
@@ -19,7 +20,7 @@ typedef $$UserEntityTableCreateCompanionBuilder = i1.UserEntityCompanion
|
||||
});
|
||||
typedef $$UserEntityTableUpdateCompanionBuilder = i1.UserEntityCompanion
|
||||
Function({
|
||||
i0.Value<String> id,
|
||||
i0.Value<i2.Uint8List> id,
|
||||
i0.Value<String> name,
|
||||
i0.Value<bool> isAdmin,
|
||||
i0.Value<String> email,
|
||||
@@ -38,7 +39,7 @@ class $$UserEntityTableFilterComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnFilters<String> get id => $composableBuilder(
|
||||
i0.ColumnFilters<i2.Uint8List> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<String> get name => $composableBuilder(
|
||||
@@ -75,7 +76,7 @@ class $$UserEntityTableOrderingComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnOrderings<String> get id => $composableBuilder(
|
||||
i0.ColumnOrderings<i2.Uint8List> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<String> get name => $composableBuilder(
|
||||
@@ -113,7 +114,7 @@ class $$UserEntityTableAnnotationComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumn<String> get id =>
|
||||
i0.GeneratedColumn<i2.Uint8List> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get name =>
|
||||
@@ -166,7 +167,7 @@ class $$UserEntityTableTableManager extends i0.RootTableManager<
|
||||
createComputedFieldComposer: () =>
|
||||
i1.$$UserEntityTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
i0.Value<String> id = const i0.Value.absent(),
|
||||
i0.Value<i2.Uint8List> 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(),
|
||||
@@ -186,7 +187,7 @@ class $$UserEntityTableTableManager extends i0.RootTableManager<
|
||||
quotaUsageInBytes: quotaUsageInBytes,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
required i2.Uint8List id,
|
||||
required String name,
|
||||
i0.Value<bool> isAdmin = const i0.Value.absent(),
|
||||
required String email,
|
||||
@@ -229,7 +230,7 @@ typedef $$UserEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||
i1.UserEntityData,
|
||||
i0.PrefetchHooks Function()>;
|
||||
|
||||
class $UserEntityTable extends i2.UserEntity
|
||||
class $UserEntityTable extends i3.UserEntity
|
||||
with i0.TableInfo<$UserEntityTable, i1.UserEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
@@ -237,9 +238,9 @@ class $UserEntityTable extends i2.UserEntity
|
||||
$UserEntityTable(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);
|
||||
late final i0.GeneratedColumn<i2.Uint8List> id =
|
||||
i0.GeneratedColumn<i2.Uint8List>('id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob, requiredDuringInsert: true);
|
||||
static const i0.VerificationMeta _nameMeta =
|
||||
const i0.VerificationMeta('name');
|
||||
@override
|
||||
@@ -255,7 +256,7 @@ class $UserEntityTable extends i2.UserEntity
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
i0.GeneratedColumn.constraintIsAlways('CHECK ("is_admin" IN (0, 1))'),
|
||||
defaultValue: const i3.Constant(false));
|
||||
defaultValue: const i4.Constant(false));
|
||||
static const i0.VerificationMeta _emailMeta =
|
||||
const i0.VerificationMeta('email');
|
||||
@override
|
||||
@@ -275,7 +276,7 @@ class $UserEntityTable extends i2.UserEntity
|
||||
i0.GeneratedColumn<DateTime>('updated_at', aliasedName, false,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: i3.currentDateAndTime);
|
||||
defaultValue: i4.currentDateAndTime);
|
||||
static const i0.VerificationMeta _quotaSizeInBytesMeta =
|
||||
const i0.VerificationMeta('quotaSizeInBytes');
|
||||
@override
|
||||
@@ -289,7 +290,7 @@ class $UserEntityTable extends i2.UserEntity
|
||||
i0.GeneratedColumn<int>('quota_usage_in_bytes', aliasedName, false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const i3.Constant(0));
|
||||
defaultValue: const i4.Constant(0));
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [
|
||||
id,
|
||||
@@ -365,7 +366,7 @@ class $UserEntityTable extends i2.UserEntity
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.UserEntityData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
.read(i0.DriftSqlType.blob, data['${effectivePrefix}id'])!,
|
||||
name: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||
isAdmin: attachedDatabase.typeMapping
|
||||
@@ -396,7 +397,7 @@ class $UserEntityTable extends i2.UserEntity
|
||||
|
||||
class UserEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.UserEntityData> {
|
||||
final String id;
|
||||
final i2.Uint8List id;
|
||||
final String name;
|
||||
final bool isAdmin;
|
||||
final String email;
|
||||
@@ -416,7 +417,7 @@ class UserEntityData extends i0.DataClass
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['id'] = i0.Variable<String>(id);
|
||||
map['id'] = i0.Variable<i2.Uint8List>(id);
|
||||
map['name'] = i0.Variable<String>(name);
|
||||
map['is_admin'] = i0.Variable<bool>(isAdmin);
|
||||
map['email'] = i0.Variable<String>(email);
|
||||
@@ -435,7 +436,7 @@ class UserEntityData extends i0.DataClass
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return UserEntityData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
id: serializer.fromJson<i2.Uint8List>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
isAdmin: serializer.fromJson<bool>(json['isAdmin']),
|
||||
email: serializer.fromJson<String>(json['email']),
|
||||
@@ -449,7 +450,7 @@ class UserEntityData extends i0.DataClass
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'id': serializer.toJson<i2.Uint8List>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'isAdmin': serializer.toJson<bool>(isAdmin),
|
||||
'email': serializer.toJson<String>(email),
|
||||
@@ -461,7 +462,7 @@ class UserEntityData extends i0.DataClass
|
||||
}
|
||||
|
||||
i1.UserEntityData copyWith(
|
||||
{String? id,
|
||||
{i2.Uint8List? id,
|
||||
String? name,
|
||||
bool? isAdmin,
|
||||
String? email,
|
||||
@@ -518,13 +519,13 @@ class UserEntityData extends i0.DataClass
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, name, isAdmin, email, profileImagePath,
|
||||
updatedAt, quotaSizeInBytes, quotaUsageInBytes);
|
||||
int get hashCode => Object.hash(i0.$driftBlobEquality.hash(id), name, isAdmin,
|
||||
email, profileImagePath, updatedAt, quotaSizeInBytes, quotaUsageInBytes);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.UserEntityData &&
|
||||
other.id == this.id &&
|
||||
i0.$driftBlobEquality.equals(other.id, this.id) &&
|
||||
other.name == this.name &&
|
||||
other.isAdmin == this.isAdmin &&
|
||||
other.email == this.email &&
|
||||
@@ -535,7 +536,7 @@ class UserEntityData extends i0.DataClass
|
||||
}
|
||||
|
||||
class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
final i0.Value<String> id;
|
||||
final i0.Value<i2.Uint8List> id;
|
||||
final i0.Value<String> name;
|
||||
final i0.Value<bool> isAdmin;
|
||||
final i0.Value<String> email;
|
||||
@@ -554,7 +555,7 @@ class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
this.quotaUsageInBytes = const i0.Value.absent(),
|
||||
});
|
||||
UserEntityCompanion.insert({
|
||||
required String id,
|
||||
required i2.Uint8List id,
|
||||
required String name,
|
||||
this.isAdmin = const i0.Value.absent(),
|
||||
required String email,
|
||||
@@ -566,7 +567,7 @@ class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
name = i0.Value(name),
|
||||
email = i0.Value(email);
|
||||
static i0.Insertable<i1.UserEntityData> custom({
|
||||
i0.Expression<String>? id,
|
||||
i0.Expression<i2.Uint8List>? id,
|
||||
i0.Expression<String>? name,
|
||||
i0.Expression<bool>? isAdmin,
|
||||
i0.Expression<String>? email,
|
||||
@@ -588,7 +589,7 @@ class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
}
|
||||
|
||||
i1.UserEntityCompanion copyWith(
|
||||
{i0.Value<String>? id,
|
||||
{i0.Value<i2.Uint8List>? id,
|
||||
i0.Value<String>? name,
|
||||
i0.Value<bool>? isAdmin,
|
||||
i0.Value<String>? email,
|
||||
@@ -612,7 +613,7 @@ class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = i0.Variable<String>(id.value);
|
||||
map['id'] = i0.Variable<i2.Uint8List>(id.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = i0.Variable<String>(name.value);
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
class UserMetadataEntity extends Table with DriftDefaultsMixin {
|
||||
const UserMetadataEntity();
|
||||
|
||||
TextColumn get userId =>
|
||||
text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
BlobColumn get userId =>
|
||||
blob().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
TextColumn get preferences => text().map(userPreferenceConverter)();
|
||||
|
||||
@override
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
as i1;
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart' as i2;
|
||||
import 'dart:typed_data' as i2;
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart' as i3;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart'
|
||||
as i3;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i4;
|
||||
import 'package:drift/internal/modular.dart' as i5;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i5;
|
||||
import 'package:drift/internal/modular.dart' as i6;
|
||||
|
||||
typedef $$UserMetadataEntityTableCreateCompanionBuilder
|
||||
= i1.UserMetadataEntityCompanion Function({
|
||||
required String userId,
|
||||
required i2.UserPreferences preferences,
|
||||
required i2.Uint8List userId,
|
||||
required i3.UserPreferences preferences,
|
||||
});
|
||||
typedef $$UserMetadataEntityTableUpdateCompanionBuilder
|
||||
= i1.UserMetadataEntityCompanion Function({
|
||||
i0.Value<String> userId,
|
||||
i0.Value<i2.UserPreferences> preferences,
|
||||
i0.Value<i2.Uint8List> userId,
|
||||
i0.Value<i3.UserPreferences> preferences,
|
||||
});
|
||||
|
||||
final class $$UserMetadataEntityTableReferences extends i0.BaseReferences<
|
||||
@@ -28,26 +29,26 @@ final class $$UserMetadataEntityTableReferences extends i0.BaseReferences<
|
||||
$$UserMetadataEntityTableReferences(
|
||||
super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static i4.$UserEntityTable _userIdTable(i0.GeneratedDatabase db) =>
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
static i5.$UserEntityTable _userIdTable(i0.GeneratedDatabase db) =>
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i5.ReadDatabaseContainer(db)
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$UserMetadataEntityTable>(
|
||||
'user_metadata_entity')
|
||||
.userId,
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.id));
|
||||
|
||||
i4.$$UserEntityTableProcessedTableManager get userId {
|
||||
final $_column = $_itemColumn<String>('user_id')!;
|
||||
i5.$$UserEntityTableProcessedTableManager get userId {
|
||||
final $_column = $_itemColumn<i2.Uint8List>('user_id')!;
|
||||
|
||||
final manager = i4
|
||||
final manager = i5
|
||||
.$$UserEntityTableTableManager(
|
||||
$_db,
|
||||
i5.ReadDatabaseContainer($_db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'))
|
||||
i6.ReadDatabaseContainer($_db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_userIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
@@ -65,26 +66,26 @@ class $$UserMetadataEntityTableFilterComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnWithTypeConverterFilters<i2.UserPreferences, i2.UserPreferences,
|
||||
i0.ColumnWithTypeConverterFilters<i3.UserPreferences, i3.UserPreferences,
|
||||
String>
|
||||
get preferences => $composableBuilder(
|
||||
column: $table.preferences,
|
||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
|
||||
|
||||
i4.$$UserEntityTableFilterComposer get userId {
|
||||
final i4.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableFilterComposer get userId {
|
||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableFilterComposer(
|
||||
i5.$$UserEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -107,20 +108,20 @@ class $$UserMetadataEntityTableOrderingComposer
|
||||
column: $table.preferences,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i4.$$UserEntityTableOrderingComposer get userId {
|
||||
final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableOrderingComposer get userId {
|
||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableOrderingComposer(
|
||||
i5.$$UserEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -139,24 +140,24 @@ class $$UserMetadataEntityTableAnnotationComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumnWithTypeConverter<i2.UserPreferences, String>
|
||||
i0.GeneratedColumnWithTypeConverter<i3.UserPreferences, String>
|
||||
get preferences => $composableBuilder(
|
||||
column: $table.preferences, builder: (column) => column);
|
||||
|
||||
i4.$$UserEntityTableAnnotationComposer get userId {
|
||||
final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
i5.$$UserEntityTableAnnotationComposer get userId {
|
||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableAnnotationComposer(
|
||||
i5.$$UserEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -192,16 +193,16 @@ class $$UserMetadataEntityTableTableManager extends i0.RootTableManager<
|
||||
i1.$$UserMetadataEntityTableAnnotationComposer(
|
||||
$db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
i0.Value<String> userId = const i0.Value.absent(),
|
||||
i0.Value<i2.UserPreferences> preferences = const i0.Value.absent(),
|
||||
i0.Value<i2.Uint8List> userId = const i0.Value.absent(),
|
||||
i0.Value<i3.UserPreferences> preferences = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.UserMetadataEntityCompanion(
|
||||
userId: userId,
|
||||
preferences: preferences,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String userId,
|
||||
required i2.UserPreferences preferences,
|
||||
required i2.Uint8List userId,
|
||||
required i3.UserPreferences preferences,
|
||||
}) =>
|
||||
i1.UserMetadataEntityCompanion.insert(
|
||||
userId: userId,
|
||||
@@ -266,7 +267,7 @@ typedef $$UserMetadataEntityTableProcessedTableManager
|
||||
i1.UserMetadataEntityData,
|
||||
i0.PrefetchHooks Function({bool userId})>;
|
||||
|
||||
class $UserMetadataEntityTable extends i3.UserMetadataEntity
|
||||
class $UserMetadataEntityTable extends i4.UserMetadataEntity
|
||||
with i0.TableInfo<$UserMetadataEntityTable, i1.UserMetadataEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
@@ -275,18 +276,18 @@ class $UserMetadataEntityTable extends i3.UserMetadataEntity
|
||||
static const i0.VerificationMeta _userIdMeta =
|
||||
const i0.VerificationMeta('userId');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> userId = i0.GeneratedColumn<String>(
|
||||
'user_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
late final i0.GeneratedColumn<i2.Uint8List> userId =
|
||||
i0.GeneratedColumn<i2.Uint8List>('user_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
@override
|
||||
late final i0.GeneratedColumnWithTypeConverter<i2.UserPreferences, String>
|
||||
late final i0.GeneratedColumnWithTypeConverter<i3.UserPreferences, String>
|
||||
preferences = i0.GeneratedColumn<String>(
|
||||
'preferences', aliasedName, false,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<i2.UserPreferences>(
|
||||
.withConverter<i3.UserPreferences>(
|
||||
i1.$UserMetadataEntityTable.$converterpreferences);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [userId, preferences];
|
||||
@@ -318,7 +319,7 @@ class $UserMetadataEntityTable extends i3.UserMetadataEntity
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.UserMetadataEntityData(
|
||||
userId: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}user_id'])!,
|
||||
.read(i0.DriftSqlType.blob, data['${effectivePrefix}user_id'])!,
|
||||
preferences: i1.$UserMetadataEntityTable.$converterpreferences.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string, data['${effectivePrefix}preferences'])!),
|
||||
@@ -330,8 +331,8 @@ class $UserMetadataEntityTable extends i3.UserMetadataEntity
|
||||
return $UserMetadataEntityTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static i0.JsonTypeConverter2<i2.UserPreferences, String, Object?>
|
||||
$converterpreferences = i3.userPreferenceConverter;
|
||||
static i0.JsonTypeConverter2<i3.UserPreferences, String, Object?>
|
||||
$converterpreferences = i4.userPreferenceConverter;
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
@override
|
||||
@@ -340,14 +341,14 @@ class $UserMetadataEntityTable extends i3.UserMetadataEntity
|
||||
|
||||
class UserMetadataEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.UserMetadataEntityData> {
|
||||
final String userId;
|
||||
final i2.UserPreferences preferences;
|
||||
final i2.Uint8List userId;
|
||||
final i3.UserPreferences preferences;
|
||||
const UserMetadataEntityData(
|
||||
{required this.userId, required this.preferences});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['user_id'] = i0.Variable<String>(userId);
|
||||
map['user_id'] = i0.Variable<i2.Uint8List>(userId);
|
||||
{
|
||||
map['preferences'] = i0.Variable<String>(
|
||||
i1.$UserMetadataEntityTable.$converterpreferences.toSql(preferences));
|
||||
@@ -359,7 +360,7 @@ class UserMetadataEntityData extends i0.DataClass
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return UserMetadataEntityData(
|
||||
userId: serializer.fromJson<String>(json['userId']),
|
||||
userId: serializer.fromJson<i2.Uint8List>(json['userId']),
|
||||
preferences: i1.$UserMetadataEntityTable.$converterpreferences
|
||||
.fromJson(serializer.fromJson<Object?>(json['preferences'])),
|
||||
);
|
||||
@@ -368,7 +369,7 @@ class UserMetadataEntityData extends i0.DataClass
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'userId': serializer.toJson<String>(userId),
|
||||
'userId': serializer.toJson<i2.Uint8List>(userId),
|
||||
'preferences': serializer.toJson<Object?>(i1
|
||||
.$UserMetadataEntityTable.$converterpreferences
|
||||
.toJson(preferences)),
|
||||
@@ -376,7 +377,7 @@ class UserMetadataEntityData extends i0.DataClass
|
||||
}
|
||||
|
||||
i1.UserMetadataEntityData copyWith(
|
||||
{String? userId, i2.UserPreferences? preferences}) =>
|
||||
{i2.Uint8List? userId, i3.UserPreferences? preferences}) =>
|
||||
i1.UserMetadataEntityData(
|
||||
userId: userId ?? this.userId,
|
||||
preferences: preferences ?? this.preferences,
|
||||
@@ -400,30 +401,31 @@ class UserMetadataEntityData extends i0.DataClass
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(userId, preferences);
|
||||
int get hashCode =>
|
||||
Object.hash(i0.$driftBlobEquality.hash(userId), preferences);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.UserMetadataEntityData &&
|
||||
other.userId == this.userId &&
|
||||
i0.$driftBlobEquality.equals(other.userId, this.userId) &&
|
||||
other.preferences == this.preferences);
|
||||
}
|
||||
|
||||
class UserMetadataEntityCompanion
|
||||
extends i0.UpdateCompanion<i1.UserMetadataEntityData> {
|
||||
final i0.Value<String> userId;
|
||||
final i0.Value<i2.UserPreferences> preferences;
|
||||
final i0.Value<i2.Uint8List> userId;
|
||||
final i0.Value<i3.UserPreferences> preferences;
|
||||
const UserMetadataEntityCompanion({
|
||||
this.userId = const i0.Value.absent(),
|
||||
this.preferences = const i0.Value.absent(),
|
||||
});
|
||||
UserMetadataEntityCompanion.insert({
|
||||
required String userId,
|
||||
required i2.UserPreferences preferences,
|
||||
required i2.Uint8List userId,
|
||||
required i3.UserPreferences preferences,
|
||||
}) : userId = i0.Value(userId),
|
||||
preferences = i0.Value(preferences);
|
||||
static i0.Insertable<i1.UserMetadataEntityData> custom({
|
||||
i0.Expression<String>? userId,
|
||||
i0.Expression<i2.Uint8List>? userId,
|
||||
i0.Expression<String>? preferences,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
@@ -433,7 +435,8 @@ class UserMetadataEntityCompanion
|
||||
}
|
||||
|
||||
i1.UserMetadataEntityCompanion copyWith(
|
||||
{i0.Value<String>? userId, i0.Value<i2.UserPreferences>? preferences}) {
|
||||
{i0.Value<i2.Uint8List>? userId,
|
||||
i0.Value<i3.UserPreferences>? preferences}) {
|
||||
return i1.UserMetadataEntityCompanion(
|
||||
userId: userId ?? this.userId,
|
||||
preferences: preferences ?? this.preferences,
|
||||
@@ -444,7 +447,7 @@ class UserMetadataEntityCompanion
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (userId.present) {
|
||||
map['user_id'] = i0.Variable<String>(userId.value);
|
||||
map['user_id'] = i0.Variable<i2.Uint8List>(userId.value);
|
||||
}
|
||||
if (preferences.present) {
|
||||
map['preferences'] = i0.Variable<String>(i1
|
||||
|
||||
@@ -3,12 +3,10 @@ import 'dart:async';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
@@ -38,8 +36,6 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
||||
LocalAlbumEntity,
|
||||
LocalAssetEntity,
|
||||
LocalAlbumAssetEntity,
|
||||
RemoteAssetEntity,
|
||||
RemoteExifEntity,
|
||||
],
|
||||
)
|
||||
class Drift extends $Drift implements IDatabaseRepository {
|
||||
|
||||
@@ -13,10 +13,6 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.d
|
||||
as i5;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||
as i6;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
||||
as i7;
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
||||
as i8;
|
||||
|
||||
abstract class $Drift extends i0.GeneratedDatabase {
|
||||
$Drift(i0.QueryExecutor e) : super(e);
|
||||
@@ -32,10 +28,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i5.$LocalAssetEntityTable(this);
|
||||
late final i6.$LocalAlbumAssetEntityTable localAlbumAssetEntity =
|
||||
i6.$LocalAlbumAssetEntityTable(this);
|
||||
late final i7.$RemoteAssetEntityTable remoteAssetEntity =
|
||||
i7.$RemoteAssetEntityTable(this);
|
||||
late final i8.$RemoteExifEntityTable remoteExifEntity =
|
||||
i8.$RemoteExifEntityTable(this);
|
||||
@override
|
||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||
@@ -47,10 +39,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
localAlbumEntity,
|
||||
localAssetEntity,
|
||||
localAlbumAssetEntity,
|
||||
remoteAssetEntity,
|
||||
remoteExifEntity,
|
||||
i5.idxLocalAssetChecksum,
|
||||
i7.uQRemoteAssetOwnerChecksum
|
||||
i5.localAssetChecksum
|
||||
];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||
@@ -94,20 +83,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('remote_asset_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('remote_asset_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('remote_exif_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
@override
|
||||
@@ -130,8 +105,4 @@ class $DriftManager {
|
||||
i5.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity);
|
||||
i6.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i6
|
||||
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
|
||||
i7.$$RemoteAssetEntityTableTableManager get remoteAssetEntity =>
|
||||
i7.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity);
|
||||
i8.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
||||
i8.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
||||
}
|
||||
|
||||
@@ -98,24 +98,12 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
||||
name: localAlbum.name,
|
||||
updatedAt: Value(localAlbum.updatedAt),
|
||||
backupSelection: localAlbum.backupSelection,
|
||||
isIosSharedAlbum: Value(localAlbum.isIosSharedAlbum),
|
||||
);
|
||||
|
||||
return _db.transaction(() async {
|
||||
await _db.localAlbumEntity
|
||||
.insertOne(companion, onConflict: DoUpdate((_) => companion));
|
||||
if (toUpsert.isNotEmpty) {
|
||||
await _upsertAssets(toUpsert);
|
||||
await _db.localAlbumAssetEntity.insertAll(
|
||||
toUpsert.map(
|
||||
(a) => LocalAlbumAssetEntityCompanion.insert(
|
||||
assetId: a.id,
|
||||
albumId: localAlbum.id,
|
||||
),
|
||||
),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
}
|
||||
await _addAssets(localAlbum.id, toUpsert);
|
||||
await _removeAssets(localAlbum.id, toDelete);
|
||||
});
|
||||
}
|
||||
@@ -134,7 +122,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
||||
name: album.name,
|
||||
updatedAt: Value(album.updatedAt),
|
||||
backupSelection: album.backupSelection,
|
||||
isIosSharedAlbum: Value(album.isIosSharedAlbum),
|
||||
marker_: const Value(null),
|
||||
);
|
||||
|
||||
@@ -239,52 +226,21 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<LocalAsset>> getAssetsToHash(String albumId) {
|
||||
final query = _db.localAlbumAssetEntity.select().join(
|
||||
[
|
||||
innerJoin(
|
||||
_db.localAssetEntity,
|
||||
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
|
||||
),
|
||||
],
|
||||
)
|
||||
..where(
|
||||
_db.localAlbumAssetEntity.albumId.equals(albumId) &
|
||||
_db.localAssetEntity.checksum.isNull(),
|
||||
)
|
||||
..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]);
|
||||
|
||||
return query
|
||||
.map((row) => row.readTable(_db.localAssetEntity).toDto())
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<void> _upsertAssets(Iterable<LocalAsset> localAssets) {
|
||||
if (localAssets.isEmpty) {
|
||||
Future<void> _addAssets(String albumId, Iterable<LocalAsset> assets) {
|
||||
if (assets.isEmpty) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
return _db.batch((batch) async {
|
||||
for (final asset in localAssets) {
|
||||
final companion = LocalAssetEntityCompanion.insert(
|
||||
name: asset.name,
|
||||
type: asset.type,
|
||||
createdAt: Value(asset.createdAt),
|
||||
updatedAt: Value(asset.updatedAt),
|
||||
durationInSeconds: Value.absentIfNull(asset.durationInSeconds),
|
||||
id: asset.id,
|
||||
checksum: const Value(null),
|
||||
);
|
||||
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
||||
_db.localAssetEntity,
|
||||
companion,
|
||||
onConflict: DoUpdate(
|
||||
(_) => companion,
|
||||
where: (old) => old.updatedAt.isNotValue(asset.updatedAt),
|
||||
return transaction(() async {
|
||||
await _upsertAssets(assets);
|
||||
await _db.localAlbumAssetEntity.insertAll(
|
||||
assets.map(
|
||||
(a) => LocalAlbumAssetEntityCompanion.insert(
|
||||
assetId: a.id,
|
||||
albumId: albumId,
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -345,14 +301,40 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
||||
return query.map((row) => row.read(assetId)!).get();
|
||||
}
|
||||
|
||||
Future<void> _upsertAssets(Iterable<LocalAsset> localAssets) {
|
||||
if (localAssets.isEmpty) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
return _db.batch((batch) async {
|
||||
batch.insertAllOnConflictUpdate(
|
||||
_db.localAssetEntity,
|
||||
localAssets.map(
|
||||
(a) => LocalAssetEntityCompanion.insert(
|
||||
name: a.name,
|
||||
type: a.type,
|
||||
createdAt: Value(a.createdAt),
|
||||
updatedAt: Value(a.updatedAt),
|
||||
durationInSeconds: Value.absentIfNull(a.durationInSeconds),
|
||||
id: a.id,
|
||||
checksum: Value.absentIfNull(a.checksum),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _deleteAssets(Iterable<String> ids) {
|
||||
if (ids.isEmpty) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
return _db.batch((batch) {
|
||||
batch.deleteWhere(_db.localAssetEntity, (f) => f.id.isIn(ids));
|
||||
});
|
||||
return _db.batch(
|
||||
(batch) => batch.deleteWhere(
|
||||
_db.localAssetEntity,
|
||||
(f) => f.id.isIn(ids),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class DriftLocalAssetRepository extends DriftDatabaseRepository
|
||||
implements ILocalAssetRepository {
|
||||
final Drift _db;
|
||||
const DriftLocalAssetRepository(this._db) : super(_db);
|
||||
|
||||
@override
|
||||
Future<void> updateHashes(Iterable<LocalAsset> hashes) {
|
||||
if (hashes.isEmpty) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
return _db.batch((batch) async {
|
||||
for (final asset in hashes) {
|
||||
batch.update(
|
||||
_db.localAssetEntity,
|
||||
LocalAssetEntityCompanion(checksum: Value(asset.checksum)),
|
||||
where: (e) => e.id.equals(asset.id),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:immich_mobile/domain/interfaces/storage.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class StorageRepository implements IStorageRepository {
|
||||
final _log = Logger('StorageRepository');
|
||||
|
||||
@override
|
||||
Future<File?> getFileForAsset(LocalAsset asset) async {
|
||||
File? file;
|
||||
try {
|
||||
final entity = await AssetEntity.fromId(asset.id);
|
||||
file = await entity?.originFile;
|
||||
if (file == null) {
|
||||
_log.warning(
|
||||
"Cannot get file for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
|
||||
);
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
_log.warning(
|
||||
"Error getting file for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -106,7 +105,6 @@ class SyncApiRepository implements ISyncApiRepository {
|
||||
stopwatch.stop();
|
||||
_logger
|
||||
.info("Remote Sync completed in ${stopwatch.elapsed.inMilliseconds}ms");
|
||||
DLog.log("Remote Sync completed in ${stopwatch.elapsed.inMilliseconds}ms");
|
||||
}
|
||||
|
||||
List<SyncEvent> _parseLines(List<String> lines) {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
|
||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart' as api show AssetVisibility;
|
||||
import 'package:openapi/api.dart' hide AssetVisibility;
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class DriftSyncStreamRepository extends DriftDatabaseRepository
|
||||
implements ISyncStreamRepository {
|
||||
@@ -24,7 +22,7 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
|
||||
for (final user in data) {
|
||||
batch.delete(
|
||||
_db.userEntity,
|
||||
UserEntityCompanion(id: Value(user.userId)),
|
||||
UserEntityCompanion(id: Value(user.userId.toUuidByte())),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -46,7 +44,7 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
|
||||
|
||||
batch.insert(
|
||||
_db.userEntity,
|
||||
companion.copyWith(id: Value(user.id)),
|
||||
companion.copyWith(id: Value(user.id.toUuidByte())),
|
||||
onConflict: DoUpdate((_) => companion),
|
||||
);
|
||||
}
|
||||
@@ -65,8 +63,8 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
|
||||
batch.delete(
|
||||
_db.partnerEntity,
|
||||
PartnerEntityCompanion(
|
||||
sharedById: Value(partner.sharedById),
|
||||
sharedWithId: Value(partner.sharedWithId),
|
||||
sharedById: Value(partner.sharedById.toUuidByte()),
|
||||
sharedWithId: Value(partner.sharedWithId.toUuidByte()),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -88,8 +86,8 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
|
||||
batch.insert(
|
||||
_db.partnerEntity,
|
||||
companion.copyWith(
|
||||
sharedById: Value(partner.sharedById),
|
||||
sharedWithId: Value(partner.sharedWithId),
|
||||
sharedById: Value(partner.sharedById.toUuidByte()),
|
||||
sharedWithId: Value(partner.sharedWithId.toUuidByte()),
|
||||
),
|
||||
onConflict: DoUpdate((_) => companion),
|
||||
);
|
||||
@@ -101,153 +99,36 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
|
||||
}
|
||||
}
|
||||
|
||||
// Assets
|
||||
@override
|
||||
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
|
||||
try {
|
||||
await _deleteAssetsV1(data);
|
||||
} catch (e, s) {
|
||||
_logger.severe('Error while processing deleteAssetsV1', e, s);
|
||||
rethrow;
|
||||
}
|
||||
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async {
|
||||
debugPrint("updateAssetsV1 - ${data.length}");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async {
|
||||
try {
|
||||
await _updateAssetsV1(data);
|
||||
} catch (e, s) {
|
||||
_logger.severe('Error while processing updateAssetsV1', e, s);
|
||||
rethrow;
|
||||
}
|
||||
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
|
||||
debugPrint("deleteAssetsV1 - ${data.length}");
|
||||
}
|
||||
|
||||
// Partner Assets
|
||||
@override
|
||||
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async {
|
||||
debugPrint("updatePartnerAssetsV1 - ${data.length}");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
|
||||
try {
|
||||
await _deleteAssetsV1(data);
|
||||
} catch (e, s) {
|
||||
_logger.severe('Error while processing deletePartnerAssetsV1', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async {
|
||||
try {
|
||||
await _updateAssetsV1(data);
|
||||
} catch (e, s) {
|
||||
_logger.severe('Error while processing updatePartnerAssetsV1', e, s);
|
||||
rethrow;
|
||||
}
|
||||
debugPrint("deletePartnerAssetsV1 - ${data.length}");
|
||||
}
|
||||
|
||||
// EXIF
|
||||
@override
|
||||
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
|
||||
try {
|
||||
await _updateAssetExifV1(data);
|
||||
} catch (e, s) {
|
||||
_logger.severe('Error while processing updateAssetsExifV1', e, s);
|
||||
rethrow;
|
||||
}
|
||||
debugPrint("updateAssetsExifV1 - ${data.length}");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
|
||||
try {
|
||||
await _updateAssetExifV1(data);
|
||||
} catch (e, s) {
|
||||
_logger.severe('Error while processing updatePartnerAssetsExifV1', e, s);
|
||||
rethrow;
|
||||
}
|
||||
debugPrint("updatePartnerAssetsExifV1 - ${data.length}");
|
||||
}
|
||||
|
||||
Future<void> _updateAssetsV1(Iterable<SyncAssetV1> data) =>
|
||||
_db.batch((batch) {
|
||||
for (final asset in data) {
|
||||
final companion = RemoteAssetEntityCompanion(
|
||||
name: Value(asset.originalFileName),
|
||||
type: Value(asset.type.toAssetType()),
|
||||
createdAt: Value.absentIfNull(asset.fileCreatedAt),
|
||||
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
|
||||
durationInSeconds: const Value(0),
|
||||
checksum: Value(asset.checksum),
|
||||
isFavorite: Value(asset.isFavorite),
|
||||
ownerId: Value(asset.ownerId),
|
||||
localDateTime: Value(asset.localDateTime),
|
||||
thumbHash: Value(asset.thumbhash),
|
||||
deletedAt: Value(asset.deletedAt),
|
||||
visibility: Value(asset.visibility.toAssetVisibility()),
|
||||
);
|
||||
|
||||
batch.insert(
|
||||
_db.remoteAssetEntity,
|
||||
companion.copyWith(id: Value(asset.id)),
|
||||
onConflict: DoUpdate((_) => companion),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Future<void> _deleteAssetsV1(Iterable<SyncAssetDeleteV1> assets) =>
|
||||
_db.batch((batch) {
|
||||
for (final asset in assets) {
|
||||
batch.delete(
|
||||
_db.remoteAssetEntity,
|
||||
RemoteAssetEntityCompanion(id: Value(asset.assetId)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Future<void> _updateAssetExifV1(Iterable<SyncAssetExifV1> data) =>
|
||||
_db.batch((batch) {
|
||||
for (final exif in data) {
|
||||
final companion = RemoteExifEntityCompanion(
|
||||
city: Value(exif.city),
|
||||
state: Value(exif.state),
|
||||
country: Value(exif.country),
|
||||
dateTimeOriginal: Value(exif.dateTimeOriginal),
|
||||
description: Value(exif.description),
|
||||
height: Value(exif.exifImageHeight),
|
||||
width: Value(exif.exifImageWidth),
|
||||
exposureTime: Value(exif.exposureTime),
|
||||
fNumber: Value(exif.fNumber),
|
||||
fileSize: Value(exif.fileSizeInByte),
|
||||
focalLength: Value(exif.focalLength),
|
||||
latitude: Value(exif.latitude),
|
||||
longitude: Value(exif.longitude),
|
||||
iso: Value(exif.iso),
|
||||
make: Value(exif.make),
|
||||
model: Value(exif.model),
|
||||
orientation: Value(exif.orientation),
|
||||
timeZone: Value(exif.timeZone),
|
||||
rating: Value(exif.rating),
|
||||
projectionType: Value(exif.projectionType),
|
||||
);
|
||||
|
||||
batch.insert(
|
||||
_db.remoteExifEntity,
|
||||
companion.copyWith(assetId: Value(exif.assetId)),
|
||||
onConflict: DoUpdate((_) => companion),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extension on AssetTypeEnum {
|
||||
AssetType toAssetType() => switch (this) {
|
||||
AssetTypeEnum.IMAGE => AssetType.image,
|
||||
AssetTypeEnum.VIDEO => AssetType.video,
|
||||
AssetTypeEnum.AUDIO => AssetType.audio,
|
||||
AssetTypeEnum.OTHER => AssetType.other,
|
||||
_ => throw Exception('Unknown AssetType value: $this'),
|
||||
};
|
||||
}
|
||||
|
||||
extension on api.AssetVisibility {
|
||||
AssetVisibility toAssetVisibility() => switch (this) {
|
||||
api.AssetVisibility.timeline => AssetVisibility.timeline,
|
||||
api.AssetVisibility.hidden => AssetVisibility.hidden,
|
||||
api.AssetVisibility.archive => AssetVisibility.archive,
|
||||
api.AssetVisibility.locked => AssetVisibility.locked,
|
||||
_ => throw Exception('Unknown AssetVisibility value: $this'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ abstract interface class IDownloadRepository {
|
||||
void Function(TaskProgressUpdate)? onTaskProgress;
|
||||
|
||||
Future<List<TaskRecord>> getLiveVideoTasks();
|
||||
Future<List<bool>> downloadAll(List<DownloadTask> tasks);
|
||||
|
||||
Future<bool> download(DownloadTask task);
|
||||
Future<bool> cancel(String id);
|
||||
Future<void> deleteAllTrackingRecords();
|
||||
Future<void> deleteRecordsWithIds(List<String> id);
|
||||
|
||||
@@ -14,7 +14,7 @@ abstract class ITimelineRepository {
|
||||
Album album,
|
||||
GroupAssetsBy groupAssetsBy,
|
||||
);
|
||||
Stream<RenderList> watchAllVideosTimeline(String userId);
|
||||
Stream<RenderList> watchAllVideosTimeline();
|
||||
|
||||
Stream<RenderList> watchHomeTimeline(
|
||||
String userId,
|
||||
|
||||
@@ -18,8 +18,8 @@ import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/locale_provider.dart';
|
||||
import 'package:immich_mobile/providers/theme.provider.dart';
|
||||
import 'package:immich_mobile/routing/app_navigation_observer.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/routing/app_navigation_observer.dart';
|
||||
import 'package:immich_mobile/services/background.service.dart';
|
||||
import 'package:immich_mobile/services/local_notification.service.dart';
|
||||
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
||||
@@ -89,6 +89,18 @@ Future<void> initApp() async {
|
||||
|
||||
initializeTimeZones();
|
||||
|
||||
FileDownloader().configureNotification(
|
||||
running: TaskNotification(
|
||||
'downloading_media'.tr(),
|
||||
'file: {filename}',
|
||||
),
|
||||
complete: TaskNotification(
|
||||
'download_finished'.tr(),
|
||||
'file: {filename}',
|
||||
),
|
||||
progressBar: true,
|
||||
);
|
||||
|
||||
await FileDownloader().trackTasksInGroup(
|
||||
downloadGroupLivePhoto,
|
||||
markDownloadedComplete: false,
|
||||
@@ -155,27 +167,10 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
||||
await ref.read(localNotificationService).setup();
|
||||
}
|
||||
|
||||
void _configureFileDownloaderNotifications() {
|
||||
FileDownloader().configureNotification(
|
||||
running: TaskNotification(
|
||||
'downloading_media'.tr(),
|
||||
'${'file_name'.tr()}: {filename}',
|
||||
),
|
||||
complete: TaskNotification(
|
||||
'download_finished'.tr(),
|
||||
'${'file_name'.tr()}: {filename}',
|
||||
),
|
||||
progressBar: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
Intl.defaultLocale = context.locale.toLanguageTag();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_configureFileDownloaderNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -3,23 +3,18 @@ import 'dart:convert';
|
||||
class AlbumViewerPageState {
|
||||
final bool isEditAlbum;
|
||||
final String editTitleText;
|
||||
final String editDescriptionText;
|
||||
|
||||
AlbumViewerPageState({
|
||||
required this.isEditAlbum,
|
||||
required this.editTitleText,
|
||||
required this.editDescriptionText,
|
||||
});
|
||||
|
||||
AlbumViewerPageState copyWith({
|
||||
bool? isEditAlbum,
|
||||
String? editTitleText,
|
||||
String? editDescriptionText,
|
||||
}) {
|
||||
return AlbumViewerPageState(
|
||||
isEditAlbum: isEditAlbum ?? this.isEditAlbum,
|
||||
editTitleText: editTitleText ?? this.editTitleText,
|
||||
editDescriptionText: editDescriptionText ?? this.editDescriptionText,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +23,6 @@ class AlbumViewerPageState {
|
||||
|
||||
result.addAll({'isEditAlbum': isEditAlbum});
|
||||
result.addAll({'editTitleText': editTitleText});
|
||||
result.addAll({'editDescriptionText': editDescriptionText});
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -37,7 +31,6 @@ class AlbumViewerPageState {
|
||||
return AlbumViewerPageState(
|
||||
isEditAlbum: map['isEditAlbum'] ?? false,
|
||||
editTitleText: map['editTitleText'] ?? '',
|
||||
editDescriptionText: map['editDescriptionText'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,7 +41,7 @@ class AlbumViewerPageState {
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText, editDescriptionText: $editDescriptionText)';
|
||||
'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -56,13 +49,9 @@ class AlbumViewerPageState {
|
||||
|
||||
return other is AlbumViewerPageState &&
|
||||
other.isEditAlbum == isEditAlbum &&
|
||||
other.editTitleText == editTitleText &&
|
||||
other.editDescriptionText == editDescriptionText;
|
||||
other.editTitleText == editTitleText;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
isEditAlbum.hashCode ^
|
||||
editTitleText.hashCode ^
|
||||
editDescriptionText.hashCode;
|
||||
int get hashCode => isEditAlbum.hashCode ^ editTitleText.hashCode;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class AssetSelectionState {
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'SelectionAssetState(hasRemote: $hasRemote, hasLocal: $hasLocal, hasMerged: $hasMerged, selectedCount: $selectedCount)';
|
||||
'SelectionAssetState(hasRemote: $hasRemote, hasMerged: $hasMerged, hasMerged: $hasMerged, selectedCount: $selectedCount)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant AssetSelectionState other) {
|
||||
|
||||
@@ -26,9 +26,9 @@ class AlbumControlButton extends ConsumerWidget {
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 16),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
height: 40,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
|
||||
@@ -30,12 +30,15 @@ class AlbumDateRange extends ConsumerWidget {
|
||||
final (startDate, endDate, shared) = data;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
padding: shared
|
||||
? const EdgeInsets.only(
|
||||
left: 16.0,
|
||||
bottom: 0.0,
|
||||
)
|
||||
: const EdgeInsets.only(left: 16.0, bottom: 8.0),
|
||||
child: Text(
|
||||
_getDateRangeText(startDate, endDate),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
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/providers/album/current_album.provider.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_viewer_editable_description.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
|
||||
class AlbumDescription extends ConsumerWidget {
|
||||
const AlbumDescription({super.key, required this.descriptionFocusNode});
|
||||
|
||||
final FocusNode descriptionFocusNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final userId = ref.watch(authProvider).userId;
|
||||
final (isOwner, isRemote, albumDescription) = ref.watch(
|
||||
currentAlbumProvider.select((album) {
|
||||
if (album == null) {
|
||||
return const (false, false, '');
|
||||
}
|
||||
|
||||
return (album.ownerId == userId, album.isRemote, album.description);
|
||||
}),
|
||||
);
|
||||
|
||||
if (isOwner && isRemote) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||
child: AlbumViewerEditableDescription(
|
||||
albumDescription: albumDescription ?? 'add_a_description'.tr(),
|
||||
descriptionFocusNode: descriptionFocusNode,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||
child: Text(
|
||||
albumDescription ?? 'add_a_description'.tr(),
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget {
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8),
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: ((context, index) {
|
||||
return Padding(
|
||||
|
||||
@@ -19,11 +19,7 @@ class AlbumTitle extends ConsumerWidget {
|
||||
return const (false, false, '');
|
||||
}
|
||||
|
||||
return (
|
||||
album.ownerId == userId,
|
||||
album.isRemote,
|
||||
album.name,
|
||||
);
|
||||
return (album.ownerId == userId, album.isRemote, album.name);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -39,12 +35,7 @@ class AlbumTitle extends ConsumerWidget {
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||
child: Text(
|
||||
albumName,
|
||||
style: context.textTheme.headlineLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
child: Text(albumName, style: context.textTheme.headlineMedium),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,10 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
|
||||
import 'package:immich_mobile/pages/album/album_control_button.dart';
|
||||
import 'package:immich_mobile/pages/album/album_date_range.dart';
|
||||
import 'package:immich_mobile/pages/album/album_description.dart';
|
||||
import 'package:immich_mobile/pages/album/album_shared_user_icons.dart';
|
||||
import 'package:immich_mobile/pages/album/album_title.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline.provider.dart';
|
||||
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
||||
import 'package:immich_mobile/providers/multiselect.provider.dart';
|
||||
@@ -37,7 +35,6 @@ class AlbumViewer extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
final titleFocusNode = useFocusNode();
|
||||
final descriptionFocusNode = useFocusNode();
|
||||
final userId = ref.watch(authProvider).userId;
|
||||
final isMultiselecting = ref.watch(multiselectProvider);
|
||||
final isProcessing = useProcessingOverlay();
|
||||
@@ -96,7 +93,6 @@ class AlbumViewer extends HookConsumerWidget {
|
||||
|
||||
onActivitiesPressed() {
|
||||
if (album.remoteId != null) {
|
||||
ref.read(currentAssetProvider.notifier).set(null);
|
||||
context.pushRoute(
|
||||
const ActivitiesRoute(),
|
||||
);
|
||||
@@ -108,44 +104,23 @@ class AlbumViewer extends HookConsumerWidget {
|
||||
MultiselectGrid(
|
||||
key: const ValueKey("albumViewerMultiselectGrid"),
|
||||
renderListProvider: albumTimelineProvider(album.id),
|
||||
topWidget: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
context.primaryColor.withValues(alpha: 0.06),
|
||||
context.primaryColor.withValues(alpha: 0.04),
|
||||
Colors.indigo.withValues(alpha: 0.02),
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: const [0.0, 0.3, 0.7, 1.0],
|
||||
topWidget: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AlbumTitle(
|
||||
key: const ValueKey("albumTitle"),
|
||||
titleFocusNode: titleFocusNode,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 32),
|
||||
const AlbumDateRange(),
|
||||
AlbumTitle(
|
||||
key: const ValueKey("albumTitle"),
|
||||
titleFocusNode: titleFocusNode,
|
||||
const AlbumDateRange(),
|
||||
const AlbumSharedUserIcons(),
|
||||
if (album.isRemote)
|
||||
AlbumControlButton(
|
||||
key: const ValueKey("albumControlButton"),
|
||||
onAddPhotosPressed: onAddPhotosPressed,
|
||||
onAddUsersPressed: onAddUsersPressed,
|
||||
),
|
||||
AlbumDescription(
|
||||
key: const ValueKey("albumDescription"),
|
||||
descriptionFocusNode: descriptionFocusNode,
|
||||
),
|
||||
const AlbumSharedUserIcons(),
|
||||
if (album.isRemote)
|
||||
AlbumControlButton(
|
||||
key: const ValueKey("albumControlButton"),
|
||||
onAddPhotosPressed: onAddPhotosPressed,
|
||||
onAddUsersPressed: onAddUsersPressed,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
onRemoveFromAlbum: onRemoveFromAlbumPressed,
|
||||
editEnabled: album.ownerId == userId,
|
||||
@@ -159,7 +134,6 @@ class AlbumViewer extends HookConsumerWidget {
|
||||
child: AlbumViewerAppbar(
|
||||
key: const ValueKey("albumViewerAppbar"),
|
||||
titleFocusNode: titleFocusNode,
|
||||
descriptionFocusNode: descriptionFocusNode,
|
||||
userId: userId,
|
||||
onAddPhotos: onAddPhotosPressed,
|
||||
onAddUsers: onAddUsersPressed,
|
||||
|
||||
@@ -14,7 +14,6 @@ import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/translation.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
||||
@@ -230,11 +229,13 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
),
|
||||
subtitle: sorted[index].ownerId != null
|
||||
? Text(
|
||||
'${t('items_count', {
|
||||
'count': sorted[index].assetCount,
|
||||
})} • ${sorted[index].ownerId != userId ? t('shared_by_user', {
|
||||
'user': sorted[index].ownerName!,
|
||||
}) : 'owned'.tr()}',
|
||||
'${(sorted[index].assetCount == 1 ? 'album_thumbnail_card_item'.tr() : 'album_thumbnail_card_items'.tr(
|
||||
namedArgs: {
|
||||
'count': sorted[index]
|
||||
.assetCount
|
||||
.toString(),
|
||||
},
|
||||
))} • ${sorted[index].ownerId != userId ? 'album_thumbnail_shared_by'.tr(namedArgs: {'user': sorted[index].ownerName!}) : 'owned'.tr()}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
context.textTheme.bodyMedium?.copyWith(
|
||||
|
||||
@@ -8,11 +8,9 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
||||
import 'package:immich_mobile/providers/album/album_viewer.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_viewer_editable_description.dart';
|
||||
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
||||
|
||||
@RoutePage()
|
||||
@@ -30,7 +28,6 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
final albumTitleController =
|
||||
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final albumTitleTextFieldFocusNode = useFocusNode();
|
||||
final albumDescriptionTextFieldFocusNode = useFocusNode();
|
||||
final isAlbumTitleTextFieldFocus = useState(false);
|
||||
final isAlbumTitleEmpty = useState(true);
|
||||
final selectedAssets = useState<Set<Asset>>(
|
||||
@@ -39,7 +36,6 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
void onBackgroundTapped() {
|
||||
albumTitleTextFieldFocusNode.unfocus();
|
||||
albumDescriptionTextFieldFocusNode.unfocus();
|
||||
isAlbumTitleTextFieldFocus.value = false;
|
||||
|
||||
if (albumTitleController.text.isEmpty) {
|
||||
@@ -81,19 +77,6 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
buildDescriptionInputField() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 10,
|
||||
),
|
||||
child: AlbumViewerEditableDescription(
|
||||
albumDescription: '',
|
||||
descriptionFocusNode: albumDescriptionTextFieldFocusNode,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
buildTitle() {
|
||||
if (selectedAssets.value.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
@@ -195,18 +178,18 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
|
||||
Future<void> createAlbum() async {
|
||||
createNonSharedAlbum() async {
|
||||
onBackgroundTapped();
|
||||
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
|
||||
ref.read(albumTitleProvider),
|
||||
ref.watch(albumTitleProvider),
|
||||
selectedAssets.value,
|
||||
);
|
||||
|
||||
if (newAlbum != null) {
|
||||
ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||
ref.watch(albumProvider.notifier).refreshRemoteAlbums();
|
||||
selectedAssets.value = {};
|
||||
ref.read(albumTitleProvider.notifier).clearAlbumTitle();
|
||||
ref.read(albumViewerProvider.notifier).disableEditAlbum();
|
||||
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
|
||||
|
||||
context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id));
|
||||
}
|
||||
}
|
||||
@@ -228,8 +211,9 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
).tr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
albumTitleController.text.isNotEmpty ? createAlbum : null,
|
||||
onPressed: albumTitleController.text.isNotEmpty
|
||||
? createNonSharedAlbum
|
||||
: null,
|
||||
child: Text(
|
||||
'create'.tr(),
|
||||
style: TextStyle(
|
||||
@@ -253,11 +237,10 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
pinned: true,
|
||||
floating: false,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(125.0),
|
||||
preferredSize: const Size.fromHeight(96.0),
|
||||
child: Column(
|
||||
children: [
|
||||
buildTitleInputField(),
|
||||
buildDescriptionInputField(),
|
||||
if (selectedAssets.value.isNotEmpty) buildControlButton(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -331,10 +331,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
pageController: controller,
|
||||
scrollPhysics: isZoomed.value
|
||||
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
|
||||
: (Platform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics() // Use heavy physics for Android
|
||||
),
|
||||
: const ClampingScrollPhysics(),
|
||||
itemCount: totalAssets.value,
|
||||
scrollDirection: Axis.horizontal,
|
||||
onPageChanged: (value) {
|
||||
|
||||
@@ -30,7 +30,7 @@ enum SettingSection {
|
||||
"backup_setting_subtitle",
|
||||
),
|
||||
languages(
|
||||
'language',
|
||||
'setting_languages_title',
|
||||
Icons.language,
|
||||
"setting_languages_subtitle",
|
||||
),
|
||||
|
||||
@@ -72,9 +72,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.router.current.name != ShareIntentRoute.name) {
|
||||
context.replaceRoute(const TabControllerRoute());
|
||||
}
|
||||
context.replaceRoute(const TabControllerRoute());
|
||||
|
||||
final hasPermission =
|
||||
await ref.read(galleryPermissionNotifier.notifier).hasPermission;
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/models/folder/recursive_folder.model.dart';
|
||||
import 'package:immich_mobile/models/folder/root_folder.model.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/folder.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
@@ -220,15 +219,12 @@ class FolderContent extends HookConsumerWidget {
|
||||
list.allAssets!.isNotEmpty)
|
||||
...list.allAssets!.map(
|
||||
(asset) => LargeLeadingTile(
|
||||
onTap: () {
|
||||
ref.read(currentAssetProvider.notifier).set(asset);
|
||||
context.pushRoute(
|
||||
GalleryViewerRoute(
|
||||
renderList: list,
|
||||
initialIndex: list.allAssets!.indexOf(asset),
|
||||
),
|
||||
);
|
||||
},
|
||||
onTap: () => context.pushRoute(
|
||||
GalleryViewerRoute(
|
||||
renderList: list,
|
||||
initialIndex: list.allAssets!.indexOf(asset),
|
||||
),
|
||||
),
|
||||
leading: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15),
|
||||
|
||||
@@ -133,7 +133,7 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
error: (error, stack) => const Text("error"),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loading: () => const CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -395,7 +395,6 @@ class _MapWithMarker extends StatelessWidget {
|
||||
children: [
|
||||
style.widgetWhen(
|
||||
onData: (style) => MapLibreMap(
|
||||
attributionButtonMargins: const Point(8, kToolbarHeight),
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: initialLocation ?? const LatLng(0, 0),
|
||||
zoom: initialLocation != null ? 12 : 0,
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/url_helper.dart';
|
||||
|
||||
@RoutePage()
|
||||
@@ -21,11 +20,6 @@ class ShareIntentPage extends HookConsumerWidget {
|
||||
final currentEndpoint = getServerUrl() ?? '--';
|
||||
final candidates = ref.watch(shareIntentUploadProvider);
|
||||
final isUploaded = useState(false);
|
||||
useOnAppLifecycleStateChange((previous, current) {
|
||||
if (current == AppLifecycleState.resumed) {
|
||||
isUploaded.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
void removeAttachment(ShareIntentAttachment attachment) {
|
||||
ref.read(shareIntentUploadProvider.notifier).removeAttachment(attachment);
|
||||
@@ -72,14 +66,6 @@ class ShareIntentPage extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
context.navigateTo(
|
||||
const TabControllerRoute(),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: attachments.length,
|
||||
|
||||
31
mobile/lib/platform/native_sync_api.g.dart
generated
31
mobile/lib/platform/native_sync_api.g.dart
generated
@@ -498,35 +498,4 @@ class NativeSyncApi {
|
||||
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<PlatformAsset>();
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Uint8List?>> hashPaths(List<String> paths) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashPaths$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture =
|
||||
pigeonVar_channel.send(<Object?>[paths]);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else if (pigeonVar_replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<Uint8List?>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
@@ -16,6 +15,7 @@ abstract final class DLog {
|
||||
static Stream<List<LogMessage>> watchLog() {
|
||||
final db = Isar.getInstance();
|
||||
if (db == null) {
|
||||
debugPrint('Isar is not initialized');
|
||||
return const Stream.empty();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ abstract final class DLog {
|
||||
static void clearLog() {
|
||||
final db = Isar.getInstance();
|
||||
if (db == null) {
|
||||
debugPrint('Isar is not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,9 +40,7 @@ abstract final class DLog {
|
||||
}
|
||||
|
||||
static void log(String message, [Object? error, StackTrace? stackTrace]) {
|
||||
if (!Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
debugPrint('[$kDevLoggerTag] [${DateTime.now()}] $message');
|
||||
}
|
||||
debugPrint('[$kDevLoggerTag] [${DateTime.now()}] $message');
|
||||
if (error != null) {
|
||||
debugPrint('Error: $error');
|
||||
}
|
||||
@@ -51,6 +50,7 @@ abstract final class DLog {
|
||||
|
||||
final isar = Isar.getInstance();
|
||||
if (isar == null) {
|
||||
debugPrint('Isar is not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,6 @@ final _features = [
|
||||
icon: Icons.photo_library_rounded,
|
||||
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(full: true),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Hash Local Assets',
|
||||
icon: Icons.numbers_outlined,
|
||||
onTap: (_, ref) => ref.read(backgroundSyncProvider).hashAssets(),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Sync Remote',
|
||||
icon: Icons.refresh_rounded,
|
||||
@@ -58,38 +53,11 @@ final _features = [
|
||||
await db.localAlbumAssetEntity.deleteAll();
|
||||
},
|
||||
),
|
||||
_Feature(
|
||||
name: 'Clear Remote Data',
|
||||
icon: Icons.delete_sweep_rounded,
|
||||
onTap: (_, ref) async {
|
||||
final db = ref.read(driftProvider);
|
||||
await db.remoteAssetEntity.deleteAll();
|
||||
await db.remoteExifEntity.deleteAll();
|
||||
},
|
||||
),
|
||||
_Feature(
|
||||
name: 'Local Media Summary',
|
||||
icon: Icons.table_chart_rounded,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const LocalMediaSummaryRoute()),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Remote Media Summary',
|
||||
icon: Icons.summarize_rounded,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const RemoteMediaSummaryRoute()),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Reset Sqlite',
|
||||
icon: Icons.table_view_rounded,
|
||||
onTap: (_, ref) async {
|
||||
final drift = ref.read(driftProvider);
|
||||
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
||||
final migrator = drift.createMigrator();
|
||||
for (final entity in drift.allSchemaEntities) {
|
||||
await migrator.drop(entity);
|
||||
await migrator.create(entity);
|
||||
}
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
@RoutePage()
|
||||
|
||||
@@ -1,48 +1,14 @@
|
||||
// ignore_for_file: prefer-single-widget-per-file
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
|
||||
class _Stat {
|
||||
const _Stat({required this.name, required this.load});
|
||||
|
||||
final String name;
|
||||
final Future<int> Function(Drift _) load;
|
||||
}
|
||||
|
||||
class _Summary extends StatelessWidget {
|
||||
final String name;
|
||||
final Future<int> countFuture;
|
||||
|
||||
const _Summary({required this.name, required this.countFuture});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<int>(
|
||||
future: countFuture,
|
||||
builder: (ctx, snapshot) {
|
||||
final Widget subtitle;
|
||||
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
subtitle = const CircularProgressIndicator();
|
||||
} else if (snapshot.hasError) {
|
||||
subtitle = const Icon(Icons.error_rounded);
|
||||
} else {
|
||||
subtitle = Text('${snapshot.data ?? 0}');
|
||||
}
|
||||
return ListTile(title: Text(name), trailing: subtitle);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final _localStats = [
|
||||
final _stats = [
|
||||
_Stat(
|
||||
name: 'Local Assets',
|
||||
load: (db) => db.managers.localAssetEntity.count(),
|
||||
@@ -70,11 +36,11 @@ class LocalMediaSummaryPage extends StatelessWidget {
|
||||
slivers: [
|
||||
SliverList.builder(
|
||||
itemBuilder: (_, index) {
|
||||
final stat = _localStats[index];
|
||||
final stat = _stats[index];
|
||||
final countFuture = stat.load(db);
|
||||
return _Summary(name: stat.name, countFuture: countFuture);
|
||||
},
|
||||
itemCount: _localStats.length,
|
||||
itemCount: _stats.length,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
@@ -93,8 +59,9 @@ class LocalMediaSummaryPage extends StatelessWidget {
|
||||
),
|
||||
FutureBuilder(
|
||||
future: albumsFuture,
|
||||
initialData: <LocalAlbum>[],
|
||||
builder: (_, snap) {
|
||||
final albums = snap.data ?? [];
|
||||
final albums = snap.data!;
|
||||
if (albums.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
@@ -123,43 +90,36 @@ class LocalMediaSummaryPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
final _remoteStats = [
|
||||
_Stat(
|
||||
name: 'Remote Assets',
|
||||
load: (db) => db.managers.remoteAssetEntity.count(),
|
||||
),
|
||||
_Stat(
|
||||
name: 'Exif Entities',
|
||||
load: (db) => db.managers.remoteExifEntity.count(),
|
||||
),
|
||||
];
|
||||
// ignore: prefer-single-widget-per-file
|
||||
class _Summary extends StatelessWidget {
|
||||
final String name;
|
||||
final Future<int> countFuture;
|
||||
|
||||
@RoutePage()
|
||||
class RemoteMediaSummaryPage extends StatelessWidget {
|
||||
const RemoteMediaSummaryPage({super.key});
|
||||
const _Summary({required this.name, required this.countFuture});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Remote Media Summary')),
|
||||
body: Consumer(
|
||||
builder: (ctx, ref, __) {
|
||||
final db = ref.watch(driftProvider);
|
||||
return FutureBuilder<int>(
|
||||
future: countFuture,
|
||||
builder: (ctx, snapshot) {
|
||||
final Widget subtitle;
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverList.builder(
|
||||
itemBuilder: (_, index) {
|
||||
final stat = _remoteStats[index];
|
||||
final countFuture = stat.load(db);
|
||||
return _Summary(name: stat.name, countFuture: countFuture);
|
||||
},
|
||||
itemCount: _remoteStats.length,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
subtitle = const CircularProgressIndicator();
|
||||
} else if (snapshot.hasError) {
|
||||
subtitle = const Icon(Icons.error_rounded);
|
||||
} else {
|
||||
subtitle = Text('${snapshot.data ?? 0}');
|
||||
}
|
||||
return ListTile(title: Text(name), trailing: subtitle);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Stat {
|
||||
const _Stat({required this.name, required this.load});
|
||||
|
||||
final String name;
|
||||
final Future<int> Function(Drift _) load;
|
||||
}
|
||||
@@ -5,13 +5,7 @@ import 'package:immich_mobile/entities/album.entity.dart';
|
||||
|
||||
class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
|
||||
AlbumViewerNotifier(this.ref)
|
||||
: super(
|
||||
AlbumViewerPageState(
|
||||
editTitleText: "",
|
||||
isEditAlbum: false,
|
||||
editDescriptionText: "",
|
||||
),
|
||||
);
|
||||
: super(AlbumViewerPageState(editTitleText: "", isEditAlbum: false));
|
||||
|
||||
final Ref ref;
|
||||
|
||||
@@ -27,24 +21,12 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
|
||||
state = state.copyWith(editTitleText: newTitle);
|
||||
}
|
||||
|
||||
void setEditDescriptionText(String newDescription) {
|
||||
state = state.copyWith(editDescriptionText: newDescription);
|
||||
}
|
||||
|
||||
void remoteEditTitleText() {
|
||||
state = state.copyWith(editTitleText: "");
|
||||
}
|
||||
|
||||
void remoteEditDescriptionText() {
|
||||
state = state.copyWith(editDescriptionText: "");
|
||||
}
|
||||
|
||||
void resetState() {
|
||||
state = state.copyWith(
|
||||
editTitleText: "",
|
||||
isEditAlbum: false,
|
||||
editDescriptionText: "",
|
||||
);
|
||||
state = state.copyWith(editTitleText: "", isEditAlbum: false);
|
||||
}
|
||||
|
||||
Future<bool> changeAlbumTitle(
|
||||
@@ -64,28 +46,6 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
|
||||
state = state.copyWith(editTitleText: "", isEditAlbum: false);
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> changeAlbumDescription(
|
||||
Album album,
|
||||
String newAlbumDescription,
|
||||
) async {
|
||||
AlbumService service = ref.watch(albumServiceProvider);
|
||||
|
||||
bool isSuccess = await service.changeDescriptionAlbum(
|
||||
album,
|
||||
newAlbumDescription,
|
||||
);
|
||||
|
||||
if (isSuccess) {
|
||||
state = state.copyWith(editDescriptionText: "", isEditAlbum: false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
state = state.copyWith(editDescriptionText: "", isEditAlbum: false);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final albumViewerProvider =
|
||||
|
||||
@@ -140,10 +140,6 @@ class DownloadStateNotifier extends StateNotifier<DownloadState> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<bool>> downloadAllAsset(List<Asset> assets) async {
|
||||
return await _downloadService.downloadAll(assets);
|
||||
}
|
||||
|
||||
void downloadAsset(Asset asset, BuildContext context) async {
|
||||
await _downloadService.download(asset);
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
|
||||
final localAssetRepository = Provider<ILocalAssetRepository>(
|
||||
(ref) => DriftLocalAssetRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
@@ -1,7 +0,0 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/storage.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||
|
||||
final storageRepositoryProvider = Provider<IStorageRepository>(
|
||||
(ref) => StorageRepository(),
|
||||
);
|
||||
@@ -1,16 +1,13 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/services/hash.service.dart';
|
||||
import 'package:immich_mobile/domain/services/local_sync.service.dart';
|
||||
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/cancel.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/storage.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||
|
||||
final syncStreamServiceProvider = Provider(
|
||||
@@ -36,12 +33,3 @@ final localSyncServiceProvider = Provider(
|
||||
storeService: ref.watch(storeServiceProvider),
|
||||
),
|
||||
);
|
||||
|
||||
final hashServiceProvider = Provider(
|
||||
(ref) => HashService(
|
||||
localAlbumRepository: ref.watch(localAlbumRepository),
|
||||
localAssetRepository: ref.watch(localAssetRepository),
|
||||
storageRepository: ref.watch(storageRepositoryProvider),
|
||||
nativeSyncApi: ref.watch(nativeSyncApiProvider),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -36,7 +36,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||
String name, {
|
||||
required Iterable<String> assetIds,
|
||||
Iterable<String> sharedUserIds = const [],
|
||||
String? description,
|
||||
}) async {
|
||||
final users = sharedUserIds.map(
|
||||
(id) => AlbumUserCreateDto(userId: id, role: AlbumUserRole.editor),
|
||||
@@ -45,7 +44,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||
_api.createAlbum(
|
||||
CreateAlbumDto(
|
||||
albumName: name,
|
||||
description: description,
|
||||
assetIds: assetIds.toList(),
|
||||
albumUsers: users.toList(),
|
||||
),
|
||||
@@ -163,7 +161,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||
lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp,
|
||||
shared: dto.shared,
|
||||
startDate: dto.startDate,
|
||||
description: dto.description,
|
||||
endDate: dto.endDate,
|
||||
activityEnabled: dto.isActivityEnabled,
|
||||
sortOrder: dto.order == AssetOrder.asc ? SortOrder.asc : SortOrder.desc,
|
||||
@@ -177,7 +174,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||
album.sharedUsers.addAll(users.map(entity.User.fromDto));
|
||||
final assets = dto.assets.map(Asset.remote).toList();
|
||||
album.assets.addAll(assets);
|
||||
|
||||
return album;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,30 +17,6 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
||||
bool get useCustomFilter =>
|
||||
Store.get(StoreKey.photoManagerCustomFilter, false);
|
||||
|
||||
FilterOptionGroup? _getAlbumFilter({
|
||||
DateTimeCond? updateTimeCond,
|
||||
bool? containsPathModified,
|
||||
List<OrderOption>? orderBy,
|
||||
}) =>
|
||||
useCustomFilter
|
||||
? FilterOptionGroup(
|
||||
imageOption: const FilterOption(
|
||||
needTitle: true,
|
||||
sizeConstraint: SizeConstraint(ignoreSize: true),
|
||||
),
|
||||
videoOption: const FilterOption(
|
||||
needTitle: true,
|
||||
sizeConstraint: SizeConstraint(ignoreSize: true),
|
||||
durationConstraint: DurationConstraint(allowNullable: true),
|
||||
),
|
||||
containsPathModified: containsPathModified ?? false,
|
||||
createTimeCond: DateTimeCond.def().copyWith(ignore: true),
|
||||
updateTimeCond:
|
||||
updateTimeCond ?? DateTimeCond.def().copyWith(ignore: true),
|
||||
orders: orderBy ?? [],
|
||||
)
|
||||
: null;
|
||||
|
||||
@override
|
||||
Future<List<Album>> getAll() async {
|
||||
final filter = useCustomFilter
|
||||
@@ -54,8 +30,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
||||
|
||||
@override
|
||||
Future<List<String>> getAssetIds(String albumId) async {
|
||||
final album =
|
||||
await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter());
|
||||
final album = await AssetPathEntity.fromId(albumId);
|
||||
final List<AssetEntity> assets =
|
||||
await album.getAssetListRange(start: 0, end: 0x7fffffffffffffff);
|
||||
return assets.map((e) => e.id).toList();
|
||||
@@ -63,8 +38,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
||||
|
||||
@override
|
||||
Future<int> getAssetCount(String albumId) async {
|
||||
final album =
|
||||
await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter());
|
||||
final album = await AssetPathEntity.fromId(albumId);
|
||||
return album.assetCountAsync;
|
||||
}
|
||||
|
||||
@@ -79,14 +53,17 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
||||
}) async {
|
||||
final onDevice = await AssetPathEntity.fromId(
|
||||
albumId,
|
||||
filterOption: _getAlbumFilter(
|
||||
filterOption: FilterOptionGroup(
|
||||
imageOption: const FilterOption(needTitle: true),
|
||||
videoOption: const FilterOption(needTitle: true),
|
||||
containsPathModified: true,
|
||||
updateTimeCond: modifiedFrom == null && modifiedUntil == null
|
||||
? null
|
||||
: DateTimeCond(
|
||||
min: modifiedFrom ?? DateTime.utc(-271820),
|
||||
max: modifiedUntil ?? DateTime.utc(275760),
|
||||
),
|
||||
orderBy: orderByModificationDate
|
||||
orders: orderByModificationDate
|
||||
? [const OrderOption(type: OrderOptionType.updateDate)]
|
||||
: [],
|
||||
),
|
||||
@@ -103,10 +80,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
||||
DateTime? modifiedFrom,
|
||||
DateTime? modifiedUntil,
|
||||
}) async {
|
||||
final assetPathEntity = await AssetPathEntity.fromId(
|
||||
id,
|
||||
filterOption: _getAlbumFilter(containsPathModified: true),
|
||||
);
|
||||
final assetPathEntity = await AssetPathEntity.fromId(id);
|
||||
return _toAlbum(assetPathEntity);
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -9,22 +8,17 @@ import 'package:immich_mobile/entities/etag.entity.dart';
|
||||
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/interfaces/auth.interface.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';
|
||||
import 'package:immich_mobile/repositories/database.repository.dart';
|
||||
|
||||
final authRepositoryProvider = Provider<IAuthRepository>(
|
||||
(ref) =>
|
||||
AuthRepository(ref.watch(dbProvider), drift: ref.watch(driftProvider)),
|
||||
(ref) => AuthRepository(ref.watch(dbProvider)),
|
||||
);
|
||||
|
||||
class AuthRepository extends DatabaseRepository implements IAuthRepository {
|
||||
final Drift _drift;
|
||||
|
||||
AuthRepository(super.db, {required Drift drift}) : _drift = drift;
|
||||
AuthRepository(super.db);
|
||||
|
||||
@override
|
||||
Future<void> clearLocalData() {
|
||||
@@ -35,8 +29,6 @@ class AuthRepository extends DatabaseRepository implements IAuthRepository {
|
||||
db.albums.clear(),
|
||||
db.eTags.clear(),
|
||||
db.users.clear(),
|
||||
_drift.remoteAssetEntity.deleteAll(),
|
||||
_drift.remoteExifEntity.deleteAll(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ class DownloadRepository implements IDownloadRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<bool>> downloadAll(List<DownloadTask> tasks) {
|
||||
return FileDownloader().enqueueAll(tasks);
|
||||
Future<bool> download(DownloadTask task) {
|
||||
return FileDownloader().enqueue(task);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -98,10 +98,8 @@ class TimelineRepository extends DatabaseRepository
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchAllVideosTimeline(String userId) {
|
||||
Stream<RenderList> watchAllVideosTimeline() {
|
||||
final query = db.assets
|
||||
.where()
|
||||
.ownerIdEqualToAnyChecksum(fastHash(userId))
|
||||
.filter()
|
||||
.isTrashedEqualTo(false)
|
||||
.visibilityEqualTo(AssetVisibilityEnum.timeline)
|
||||
|
||||
@@ -64,7 +64,7 @@ import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/local_media_stat.page.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||
import 'package:immich_mobile/routing/auth_guard.dart';
|
||||
@@ -326,9 +326,5 @@ class AppRouter extends RootStackRouter {
|
||||
page: LocalMediaSummaryRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: RemoteMediaSummaryRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1356,22 +1356,6 @@ class RecentlyTakenRoute extends PageRouteInfo<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [RemoteMediaSummaryPage]
|
||||
class RemoteMediaSummaryRoute extends PageRouteInfo<void> {
|
||||
const RemoteMediaSummaryRoute({List<PageRouteInfo>? children})
|
||||
: super(RemoteMediaSummaryRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'RemoteMediaSummaryRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const RemoteMediaSummaryPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [SearchPage]
|
||||
class SearchRoute extends PageRouteInfo<SearchRouteArgs> {
|
||||
|
||||
@@ -422,25 +422,6 @@ class AlbumService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> changeDescriptionAlbum(
|
||||
Album album,
|
||||
String newAlbumDescription,
|
||||
) async {
|
||||
try {
|
||||
final updatedAlbum = await _albumApiRepository.update(
|
||||
album.remoteId!,
|
||||
description: newAlbumDescription,
|
||||
);
|
||||
|
||||
album.description = updatedAlbum.description;
|
||||
await _albumRepository.update(album);
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint("Error changeDescriptionAlbum ${e.toString()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Album?> getAlbumByName(
|
||||
String name, {
|
||||
bool? remote,
|
||||
|
||||
@@ -104,7 +104,7 @@ class AppSettingsService {
|
||||
return Store.get(setting.storeKey, setting.defaultValue);
|
||||
}
|
||||
|
||||
Future<void> setSetting<T>(AppSettingsEnum<T> setting, T value) {
|
||||
return Store.put(setting.storeKey, value);
|
||||
void setSetting<T>(AppSettingsEnum<T> setting, T value) {
|
||||
Store.put(setting.storeKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,19 +159,9 @@ class DownloadService {
|
||||
return await FileDownloader().cancelTaskWithId(id);
|
||||
}
|
||||
|
||||
Future<List<bool>> downloadAll(List<Asset> assets) async {
|
||||
return await _downloadRepository
|
||||
.downloadAll(assets.expand(_createDownloadTasks).toList());
|
||||
}
|
||||
|
||||
Future<void> download(Asset asset) async {
|
||||
final tasks = _createDownloadTasks(asset);
|
||||
await _downloadRepository.downloadAll(tasks);
|
||||
}
|
||||
|
||||
List<DownloadTask> _createDownloadTasks(Asset asset) {
|
||||
if (asset.isImage && asset.livePhotoVideoId != null && Platform.isIOS) {
|
||||
return [
|
||||
await _downloadRepository.download(
|
||||
_buildDownloadTask(
|
||||
asset.remoteId!,
|
||||
asset.fileName,
|
||||
@@ -181,6 +171,9 @@ class DownloadService {
|
||||
id: asset.remoteId!,
|
||||
).toJson(),
|
||||
),
|
||||
);
|
||||
|
||||
await _downloadRepository.download(
|
||||
_buildDownloadTask(
|
||||
asset.livePhotoVideoId!,
|
||||
asset.fileName
|
||||
@@ -192,20 +185,16 @@ class DownloadService {
|
||||
id: asset.remoteId!,
|
||||
).toJson(),
|
||||
),
|
||||
];
|
||||
);
|
||||
} else {
|
||||
await _downloadRepository.download(
|
||||
_buildDownloadTask(
|
||||
asset.remoteId!,
|
||||
asset.fileName,
|
||||
group: asset.isImage ? downloadGroupImage : downloadGroupVideo,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (asset.remoteId == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
_buildDownloadTask(
|
||||
asset.remoteId!,
|
||||
asset.fileName,
|
||||
group: asset.isImage ? downloadGroupImage : downloadGroupVideo,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
DownloadTask _buildDownloadTask(
|
||||
|
||||
@@ -451,7 +451,6 @@ class SyncService {
|
||||
final usersToLink = await _userRepository.getByUserIds(userIdsToAdd);
|
||||
|
||||
album.name = dto.name;
|
||||
album.description = dto.description;
|
||||
album.shared = dto.shared;
|
||||
album.createdAt = dto.createdAt;
|
||||
album.modifiedAt = dto.modifiedAt;
|
||||
@@ -644,7 +643,6 @@ class SyncService {
|
||||
toUpdate.isEmpty &&
|
||||
toDelete.isEmpty &&
|
||||
dbAlbum.name == deviceAlbum.name &&
|
||||
dbAlbum.description == deviceAlbum.description &&
|
||||
dbAlbum.modifiedAt.isAtSameMomentAs(deviceAlbum.modifiedAt)) {
|
||||
// changes only affeted excluded albums
|
||||
_log.info(
|
||||
@@ -672,7 +670,6 @@ class SyncService {
|
||||
deleteCandidates.addAll(toDelete);
|
||||
existing.addAll(existingInDb);
|
||||
dbAlbum.name = deviceAlbum.name;
|
||||
dbAlbum.description = deviceAlbum.description;
|
||||
dbAlbum.modifiedAt = deviceAlbum.modifiedAt;
|
||||
if (dbAlbum.thumbnail.value != null &&
|
||||
toDelete.contains(dbAlbum.thumbnail.value)) {
|
||||
@@ -946,7 +943,6 @@ class SyncService {
|
||||
Album dbAlbum,
|
||||
) async {
|
||||
return deviceAlbum.name != dbAlbum.name ||
|
||||
deviceAlbum.description != dbAlbum.description ||
|
||||
!deviceAlbum.modifiedAt.isAtSameMomentAs(dbAlbum.modifiedAt) ||
|
||||
await _albumMediaRepository.getAssetCount(deviceAlbum.localId!) !=
|
||||
(await _eTagRepository.getById(deviceAlbum.eTagKeyAssetCount))
|
||||
@@ -1105,7 +1101,6 @@ class SyncService {
|
||||
bool _hasRemoteAlbumChanged(Album remoteAlbum, Album dbAlbum) {
|
||||
return remoteAlbum.remoteAssetCount != dbAlbum.assetCount ||
|
||||
remoteAlbum.name != dbAlbum.name ||
|
||||
remoteAlbum.description != dbAlbum.description ||
|
||||
remoteAlbum.remoteThumbnailAssetId != dbAlbum.thumbnail.value?.remoteId ||
|
||||
remoteAlbum.shared != dbAlbum.shared ||
|
||||
remoteAlbum.remoteUsers.length != dbAlbum.sharedUsers.length ||
|
||||
|
||||
@@ -75,9 +75,7 @@ class TimelineService {
|
||||
}
|
||||
|
||||
Stream<RenderList> watchAllVideosTimeline() {
|
||||
final user = _userService.getMyUser();
|
||||
|
||||
return _timelineRepository.watchAllVideosTimeline(user.id);
|
||||
return _timelineRepository.watchAllVideosTimeline();
|
||||
}
|
||||
|
||||
Future<RenderList> getTimelineFromAssets(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user