Compare commits

..

1 Commits

Author SHA1 Message Date
Alex
adb59cf3a0 chore: flutter 3.32 2025-05-29 13:37:23 -05:00
367 changed files with 4983 additions and 12671 deletions

118
.github/actions/image-build/action.yml vendored Normal file
View 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
View 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

View File

@@ -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);
};

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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,

View File

@@ -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

View File

@@ -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', () => {

View File

@@ -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: [],

View File

@@ -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',

View File

@@ -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/);
});

View File

@@ -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": "Youre 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 were committed not to add paywalls, this purchase will not grant you any additional features in Immich. We rely on users like you to support Immichs 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",

View File

@@ -1,3 +1,3 @@
{
"flutter": "3.29.3"
}
"flutter": "3.32.0"
}

View File

@@ -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

View File

@@ -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)
}
}
}
}
}

View File

@@ -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
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -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"

View File

@@ -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)
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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'),

View File

@@ -29,8 +29,6 @@ abstract interface class ILocalAlbumRepository implements IDatabaseRepository {
String albumId,
Iterable<String> assetIdsToKeep,
);
Future<List<LocalAsset>> getAssetsToHash(String albumId);
}
enum SortLocalAlbumsBy { id }

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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
}''';
}
}

View File

@@ -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});
}

View File

@@ -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()

View File

@@ -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());

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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');

View File

@@ -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);
}

View File

@@ -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};
}

File diff suppressed because it is too large Load Diff

View File

@@ -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()();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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> {

View File

@@ -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))();

View File

@@ -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);

View File

@@ -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

View File

@@ -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()();

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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),
),
);
}
}

View File

@@ -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),
);
}
});
}
}

View File

@@ -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;
}
}

View 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) {

View File

@@ -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'),
};
}

View File

@@ -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);

View File

@@ -14,7 +14,7 @@ abstract class ITimelineRepository {
Album album,
GroupAssetsBy groupAssetsBy,
);
Stream<RenderList> watchAllVideosTimeline(String userId);
Stream<RenderList> watchAllVideosTimeline();
Stream<RenderList> watchHomeTimeline(
String userId,

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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: [

View File

@@ -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,
),
);
}

View File

@@ -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,
),
);
}
}

View File

@@ -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(

View File

@@ -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),
);
}
}

View File

@@ -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,

View File

@@ -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(

View File

@@ -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(),
],
),

View File

@@ -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) {

View File

@@ -30,7 +30,7 @@ enum SettingSection {
"backup_setting_subtitle",
),
languages(
'language',
'setting_languages_title',
Icons.language,
"setting_languages_subtitle",
),

View File

@@ -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;

View File

@@ -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),

View File

@@ -133,7 +133,7 @@ class PeopleCollectionPage extends HookConsumerWidget {
);
},
error: (error, stack) => const Text("error"),
loading: () => const Center(child: CircularProgressIndicator()),
loading: () => const CircularProgressIndicator(),
),
);
},

View File

@@ -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,

View File

@@ -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,

View File

@@ -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?>();
}
}
}

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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 =

View File

@@ -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);
}

View File

@@ -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)),
);

View File

@@ -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(),
);

View File

@@ -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),
),
);

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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(),
]);
});
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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],
),
];
}

View File

@@ -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> {

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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(

View File

@@ -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 ||

View File

@@ -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